Files
mostlymatter/server/einterfaces/mocks/ClusterInterface.go
Agniva De Sarker cb75a20c54 MM-61904: Make reliable websockets work in HA (#29489)
We do a cluster request to get the active and dead queues
from other nodes in the cluster to sync any missing
information.

We check the dead queue in the other nodes to see
if there's been any message loss or not. Accordingly,
we send just the active queue or both active and dead queues.

There's still an edge case that is left out where
a client could have potentially connected and reconnected
to multiple nodes leaving multiple active queues
in multiple nodes. We don't handle this scenario
because then potentially we need to create
a slice of sendQueueSize * number_of_nodes. And then
this can happen again, leading to an infinite increase
in sendQueueSize.

We leave this edge-case to Redis, acknowledging
a limitation in our architecture.

In this PR, when there's no message loss, we just
take the active queue from the last node it connected
to.

And if there's message loss where the client's
seqNum is within the last node's dead queue, we also
handle that.

But if there's severe message loss where the client's
seqNum falls within the dead queue of another node, then
we just send the data from that node to reconstruct the
data as much as possible. It could be possible to set
a new connection ID in this case, but this involves
more data transfer always from all nodes and recomputing
the state in the requestor node.

https://mattermost.atlassian.net/browse/MM-61904

```release-note
NONE
```

Co-authored-by: Mattermost Build <build@mattermost.com>
2025-01-17 11:11:32 +05:30

409 строки
10 KiB
Go

// Code generated by mockery v2.42.2. DO NOT EDIT.
// Regenerate this file using `make einterfaces-mocks`.
package mocks
import (
einterfaces "github.com/mattermost/mattermost/server/v8/einterfaces"
mock "github.com/stretchr/testify/mock"
model "github.com/mattermost/mattermost/server/public/model"
request "github.com/mattermost/mattermost/server/public/shared/request"
)
// ClusterInterface is an autogenerated mock type for the ClusterInterface type
type ClusterInterface struct {
mock.Mock
}
// ConfigChanged provides a mock function with given fields: previousConfig, newConfig, sendToOtherServer
func (_m *ClusterInterface) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError {
ret := _m.Called(previousConfig, newConfig, sendToOtherServer)
if len(ret) == 0 {
panic("no return value specified for ConfigChanged")
}
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(*model.Config, *model.Config, bool) *model.AppError); ok {
r0 = rf(previousConfig, newConfig, sendToOtherServer)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}
// GenerateSupportPacket provides a mock function with given fields: rctx, options
func (_m *ClusterInterface) GenerateSupportPacket(rctx request.CTX, options *model.SupportPacketOptions) (map[string][]model.FileData, error) {
ret := _m.Called(rctx, options)
if len(ret) == 0 {
panic("no return value specified for GenerateSupportPacket")
}
var r0 map[string][]model.FileData
var r1 error
if rf, ok := ret.Get(0).(func(request.CTX, *model.SupportPacketOptions) (map[string][]model.FileData, error)); ok {
return rf(rctx, options)
}
if rf, ok := ret.Get(0).(func(request.CTX, *model.SupportPacketOptions) map[string][]model.FileData); ok {
r0 = rf(rctx, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string][]model.FileData)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, *model.SupportPacketOptions) error); ok {
r1 = rf(rctx, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetClusterId provides a mock function with given fields:
func (_m *ClusterInterface) GetClusterId() string {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetClusterId")
}
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// GetClusterInfos provides a mock function with given fields:
func (_m *ClusterInterface) GetClusterInfos() []*model.ClusterInfo {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetClusterInfos")
}
var r0 []*model.ClusterInfo
if rf, ok := ret.Get(0).(func() []*model.ClusterInfo); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.ClusterInfo)
}
}
return r0
}
// GetClusterStats provides a mock function with given fields: rctx
func (_m *ClusterInterface) GetClusterStats(rctx request.CTX) ([]*model.ClusterStats, *model.AppError) {
ret := _m.Called(rctx)
if len(ret) == 0 {
panic("no return value specified for GetClusterStats")
}
var r0 []*model.ClusterStats
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX) ([]*model.ClusterStats, *model.AppError)); ok {
return rf(rctx)
}
if rf, ok := ret.Get(0).(func(request.CTX) []*model.ClusterStats); ok {
r0 = rf(rctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.ClusterStats)
}
}
if rf, ok := ret.Get(1).(func(request.CTX) *model.AppError); ok {
r1 = rf(rctx)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetLogs provides a mock function with given fields: ctx, page, perPage
func (_m *ClusterInterface) GetLogs(ctx request.CTX, page int, perPage int) ([]string, *model.AppError) {
ret := _m.Called(ctx, page, perPage)
if len(ret) == 0 {
panic("no return value specified for GetLogs")
}
var r0 []string
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, int, int) ([]string, *model.AppError)); ok {
return rf(ctx, page, perPage)
}
if rf, ok := ret.Get(0).(func(request.CTX, int, int) []string); ok {
r0 = rf(ctx, page, perPage)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, int, int) *model.AppError); ok {
r1 = rf(ctx, page, perPage)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetMyClusterInfo provides a mock function with given fields:
func (_m *ClusterInterface) GetMyClusterInfo() *model.ClusterInfo {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetMyClusterInfo")
}
var r0 *model.ClusterInfo
if rf, ok := ret.Get(0).(func() *model.ClusterInfo); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.ClusterInfo)
}
}
return r0
}
// GetPluginStatuses provides a mock function with given fields:
func (_m *ClusterInterface) GetPluginStatuses() (model.PluginStatuses, *model.AppError) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetPluginStatuses")
}
var r0 model.PluginStatuses
var r1 *model.AppError
if rf, ok := ret.Get(0).(func() (model.PluginStatuses, *model.AppError)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() model.PluginStatuses); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(model.PluginStatuses)
}
}
if rf, ok := ret.Get(1).(func() *model.AppError); ok {
r1 = rf()
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetWSQueues provides a mock function with given fields: userID, connectionID, seqNum
func (_m *ClusterInterface) GetWSQueues(userID string, connectionID string, seqNum int64) (map[string]*model.WSQueues, error) {
ret := _m.Called(userID, connectionID, seqNum)
if len(ret) == 0 {
panic("no return value specified for GetWSQueues")
}
var r0 map[string]*model.WSQueues
var r1 error
if rf, ok := ret.Get(0).(func(string, string, int64) (map[string]*model.WSQueues, error)); ok {
return rf(userID, connectionID, seqNum)
}
if rf, ok := ret.Get(0).(func(string, string, int64) map[string]*model.WSQueues); ok {
r0 = rf(userID, connectionID, seqNum)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]*model.WSQueues)
}
}
if rf, ok := ret.Get(1).(func(string, string, int64) error); ok {
r1 = rf(userID, connectionID, seqNum)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HealthScore provides a mock function with given fields:
func (_m *ClusterInterface) HealthScore() int {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for HealthScore")
}
var r0 int
if rf, ok := ret.Get(0).(func() int); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int)
}
return r0
}
// IsLeader provides a mock function with given fields:
func (_m *ClusterInterface) IsLeader() bool {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for IsLeader")
}
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// NotifyMsg provides a mock function with given fields: buf
func (_m *ClusterInterface) NotifyMsg(buf []byte) {
_m.Called(buf)
}
// QueryLogs provides a mock function with given fields: rctx, page, perPage
func (_m *ClusterInterface) QueryLogs(rctx request.CTX, page int, perPage int) (map[string][]string, *model.AppError) {
ret := _m.Called(rctx, page, perPage)
if len(ret) == 0 {
panic("no return value specified for QueryLogs")
}
var r0 map[string][]string
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, int, int) (map[string][]string, *model.AppError)); ok {
return rf(rctx, page, perPage)
}
if rf, ok := ret.Get(0).(func(request.CTX, int, int) map[string][]string); ok {
r0 = rf(rctx, page, perPage)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string][]string)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, int, int) *model.AppError); ok {
r1 = rf(rctx, page, perPage)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// RegisterClusterMessageHandler provides a mock function with given fields: event, crm
func (_m *ClusterInterface) RegisterClusterMessageHandler(event model.ClusterEvent, crm einterfaces.ClusterMessageHandler) {
_m.Called(event, crm)
}
// SendClusterMessage provides a mock function with given fields: msg
func (_m *ClusterInterface) SendClusterMessage(msg *model.ClusterMessage) {
_m.Called(msg)
}
// SendClusterMessageToNode provides a mock function with given fields: nodeID, msg
func (_m *ClusterInterface) SendClusterMessageToNode(nodeID string, msg *model.ClusterMessage) error {
ret := _m.Called(nodeID, msg)
if len(ret) == 0 {
panic("no return value specified for SendClusterMessageToNode")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, *model.ClusterMessage) error); ok {
r0 = rf(nodeID, msg)
} else {
r0 = ret.Error(0)
}
return r0
}
// StartInterNodeCommunication provides a mock function with given fields:
func (_m *ClusterInterface) StartInterNodeCommunication() {
_m.Called()
}
// StopInterNodeCommunication provides a mock function with given fields:
func (_m *ClusterInterface) StopInterNodeCommunication() {
_m.Called()
}
// WebConnCountForUser provides a mock function with given fields: userID
func (_m *ClusterInterface) WebConnCountForUser(userID string) (int, *model.AppError) {
ret := _m.Called(userID)
if len(ret) == 0 {
panic("no return value specified for WebConnCountForUser")
}
var r0 int
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(string) (int, *model.AppError)); ok {
return rf(userID)
}
if rf, ok := ret.Get(0).(func(string) int); ok {
r0 = rf(userID)
} else {
r0 = ret.Get(0).(int)
}
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
r1 = rf(userID)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// NewClusterInterface creates a new instance of ClusterInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewClusterInterface(t interface {
mock.TestingT
Cleanup(func())
}) *ClusterInterface {
mock := &ClusterInterface{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}