mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-54593] HA aware Support Packet (#27598)
This commit is contained in:
@@ -1138,7 +1138,7 @@
|
||||
- name: plugin_packets
|
||||
in: query
|
||||
description: |
|
||||
Specifies plugin identifiers whose content should be included in the support packet.
|
||||
Specifies plugin identifiers whose content should be included in the Support Packet.
|
||||
|
||||
__Minimum server version__: 9.8.0
|
||||
required: false
|
||||
|
||||
@@ -49,6 +49,6 @@ const goToSupportPacketGenerationModal = () => {
|
||||
cy.findByRole('button', {name: 'Menu Icon'}).should('exist').click();
|
||||
cy.findByRole('button', {name: 'Commercial Support dialog'}).click();
|
||||
|
||||
// * Ensure the download support packet button exist and that text regarding setting the proper settings exist
|
||||
// * Ensure the download Support Packet button exist and that text regarding setting the proper settings exist
|
||||
cy.get('a.DownloadSupportPacket').should('exist');
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/audit"
|
||||
"github.com/mattermost/mattermost/server/v8/config"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/cache"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/upgrader"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/shared/web"
|
||||
@@ -84,7 +85,7 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Support packet generation is limited to system admins (MM-42271).
|
||||
// Support Packet generation is limited to system admins (MM-42271).
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
@@ -427,14 +428,14 @@ func downloadLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
fileData, err := c.App.GetMattermostLog(c.AppContext)
|
||||
fileData, err := c.App.Srv().Platform().GetLogFile(c.AppContext)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("downloadLogs", "api.system.logs.download_bytes_buffer.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(fileData.Body)
|
||||
web.WriteFileResponse("mattermost.log",
|
||||
web.WriteFileResponse(config.LogFilename,
|
||||
"text/plain",
|
||||
int64(len(fileData.Body)),
|
||||
time.Now(),
|
||||
|
||||
@@ -211,7 +211,7 @@ func TestGenerateSupportPacket(t *testing.T) {
|
||||
th.LoginSystemManager()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("system admin and local client can generate support packet", func(t *testing.T) {
|
||||
t.Run("system admin and local client can generate Support Packet", func(t *testing.T) {
|
||||
l := model.NewTestLicense()
|
||||
th.App.Srv().SetLicense(l)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ func (a *App) getAnalytics(rctx request.CTX, name string, teamID string, forSupp
|
||||
}
|
||||
|
||||
skipIntensiveQueries := false
|
||||
// When generating a Support packet, always run intensive queries.
|
||||
// When generating a Support Packet, always run intensive queries.
|
||||
if !forSupportPacket {
|
||||
if systemUserCount > int64(*a.Config().AnalyticsSettings.MaxUsersForStatistics) {
|
||||
rctx.Logger().Debug("More than limit users are on the system, intensive queries skipped", mlog.Int("limit", *a.Config().AnalyticsSettings.MaxUsersForStatistics))
|
||||
|
||||
@@ -731,7 +731,6 @@ type AppIface interface {
|
||||
GetLatestVersion(rctx request.CTX, latestVersionUrl string) (*model.GithubReleaseInfo, *model.AppError)
|
||||
GetLogs(rctx request.CTX, page, perPage int) ([]string, *model.AppError)
|
||||
GetLogsSkipSend(rctx request.CTX, page, perPage int, logFilter *model.LogFilter) ([]string, *model.AppError)
|
||||
GetMattermostLog(ctx request.CTX) (*model.FileData, error)
|
||||
GetMemberCountsByGroup(rctx request.CTX, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, *model.AppError)
|
||||
GetMessageForNotification(post *model.Post, teamName, siteUrl string, translateFunc i18n.TranslateFunc) string
|
||||
GetMultipleEmojiByName(c request.CTX, names []string) ([]*model.Emoji, *model.AppError)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
||||
)
|
||||
|
||||
@@ -146,6 +147,9 @@ func (c *ClusterMock) GetLogs(page, perPage int) ([]string, *model.AppError) {
|
||||
func (c *ClusterMock) QueryLogs(page, perPage int) (map[string][]string, *model.AppError) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *ClusterMock) GenerateSupportPacket(rctx request.CTX, options *model.SupportPacketOptions) (map[string][]model.FileData, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *ClusterMock) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { return nil, nil }
|
||||
func (c *ClusterMock) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError {
|
||||
return nil
|
||||
|
||||
@@ -7660,28 +7660,6 @@ func (a *OpenTracingAppLayer) GetMarketplacePlugins(rctx request.CTX, filter *mo
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetMattermostLog(ctx request.CTX) (*model.FileData, error) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetMattermostLog")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetMattermostLog(ctx)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetMemberCountsByGroup(rctx request.CTX, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetMemberCountsByGroup")
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
||||
)
|
||||
|
||||
@@ -144,6 +145,9 @@ func (c *ClusterMock) GetLogs(page, perPage int) ([]string, *model.AppError)
|
||||
func (c *ClusterMock) QueryLogs(page, perPage int) (map[string][]string, *model.AppError) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *ClusterMock) GenerateSupportPacket(rctx request.CTX, options *model.SupportPacketOptions) (map[string][]model.FileData, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *ClusterMock) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { return nil, nil }
|
||||
func (c *ClusterMock) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError {
|
||||
return nil
|
||||
|
||||
@@ -6,14 +6,16 @@ package platform
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/config"
|
||||
)
|
||||
|
||||
@@ -206,6 +208,40 @@ func (ps *PlatformService) GetLogsSkipSend(page, perPage int, logFilter *model.L
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func (ps *PlatformService) GetLogFile(_ request.CTX) (*model.FileData, error) {
|
||||
if !*ps.Config().LogSettings.EnableFile {
|
||||
return nil, errors.New("Unable to retrieve mattermost logs because LogSettings.EnableFile is set to false")
|
||||
}
|
||||
|
||||
mattermostLog := config.GetLogFileLocation(*ps.Config().LogSettings.FileLocation)
|
||||
mattermostLogFileData, err := os.ReadFile(mattermostLog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed read mattermost log file at path %s", mattermostLog)
|
||||
}
|
||||
|
||||
return &model.FileData{
|
||||
Filename: config.LogFilename,
|
||||
Body: mattermostLogFileData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ps *PlatformService) GetNotificationLogFile(_ request.CTX) (*model.FileData, error) {
|
||||
if !*ps.Config().NotificationLogSettings.EnableFile {
|
||||
return nil, errors.New("Unable to retrieve notifications logs because NotificationLogSettings.EnableFile is set to false")
|
||||
}
|
||||
|
||||
notificationsLog := config.GetNotificationsLogFileLocation(*ps.Config().LogSettings.FileLocation)
|
||||
notificationsLogFileData, err := os.ReadFile(notificationsLog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed read notifcation log file at path %s", notificationsLog)
|
||||
}
|
||||
|
||||
return &model.FileData{
|
||||
Filename: config.LogNotificationFilename,
|
||||
Body: notificationsLogFileData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isLogFilteredByLevel(logFilter *model.LogFilter, entry *model.LogEntry) bool {
|
||||
logLevels := logFilter.LogLevels
|
||||
if len(logLevels) == 0 {
|
||||
|
||||
104
server/channels/app/platform/log_test.go
Normal file
104
server/channels/app/platform/log_test.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetMattermostLog(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
// disable mattermost log file setting in config so we should get an warning
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.EnableFile = false
|
||||
})
|
||||
|
||||
fileData, err := th.Service.GetLogFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "Unable to retrieve mattermost logs because LogSettings.EnableFile is set to false")
|
||||
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(dir)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Enable log file but point to an empty directory to get an error trying to read the file
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.EnableFile = true
|
||||
*cfg.LogSettings.FileLocation = dir
|
||||
})
|
||||
|
||||
logLocation := config.GetLogFileLocation(dir)
|
||||
|
||||
// There is no mattermost.log file yet, so this fails
|
||||
fileData, err = th.Service.GetLogFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "failed read mattermost log file at path "+logLocation)
|
||||
|
||||
// Happy path where we get a log file and no warning
|
||||
d1 := []byte("hello\ngo\n")
|
||||
err = os.WriteFile(logLocation, d1, 0777)
|
||||
require.NoError(t, err)
|
||||
|
||||
fileData, err = th.Service.GetLogFile(th.Context)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, fileData)
|
||||
assert.Equal(t, "mattermost.log", fileData.Filename)
|
||||
assert.Positive(t, len(fileData.Body))
|
||||
}
|
||||
|
||||
func TestGetNotificationLogFile(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
// Disable notifications file setting in config so we should get an warning
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.EnableFile = false
|
||||
})
|
||||
|
||||
fileData, err := th.Service.GetNotificationLogFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "Unable to retrieve notifications logs because NotificationLogSettings.EnableFile is set to false")
|
||||
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(dir)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Enable notifications file but point to an empty directory to get an error trying to read the file
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.EnableFile = true
|
||||
*cfg.LogSettings.FileLocation = dir
|
||||
})
|
||||
|
||||
logLocation := config.GetNotificationsLogFileLocation(dir)
|
||||
|
||||
// There is no notifications.log file yet, so this fails
|
||||
fileData, err = th.Service.GetNotificationLogFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "failed read notifcation log file at path "+logLocation)
|
||||
|
||||
// Happy path where we have file and no error
|
||||
d1 := []byte("hello\ngo\n")
|
||||
err = os.WriteFile(logLocation, d1, 0777)
|
||||
require.NoError(t, err)
|
||||
|
||||
fileData, err = th.Service.GetNotificationLogFile(th.Context)
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, fileData)
|
||||
assert.Equal(t, "notifications.log", fileData.Filename)
|
||||
assert.Positive(t, len(fileData.Body))
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"net/http/pprof"
|
||||
"path"
|
||||
"runtime"
|
||||
rpprof "runtime/pprof"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
@@ -23,11 +25,15 @@ import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
||||
)
|
||||
|
||||
const TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second
|
||||
const (
|
||||
TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second
|
||||
cpuProfileDuration = 5 * time.Second
|
||||
)
|
||||
|
||||
type platformMetrics struct {
|
||||
server *http.Server
|
||||
@@ -247,3 +253,52 @@ func (ps *PlatformService) Metrics() einterfaces.MetricsInterface {
|
||||
|
||||
return ps.metricsIFace
|
||||
}
|
||||
|
||||
func (ps *PlatformService) CreateCPUProfile(_ request.CTX) (*model.FileData, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
err := rpprof.StartCPUProfile(&b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to start CPU profile")
|
||||
}
|
||||
|
||||
time.Sleep(cpuProfileDuration)
|
||||
|
||||
rpprof.StopCPUProfile()
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "cpu.prof",
|
||||
Body: b.Bytes(),
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
func (ps *PlatformService) CreateHeapProfile(_ request.CTX) (*model.FileData, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
err := rpprof.Lookup("heap").WriteTo(&b, 0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lookup heap profile")
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "heap.prof",
|
||||
Body: b.Bytes(),
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
func (ps *PlatformService) CreateGoroutineProfile(_ request.CTX) (*model.FileData, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
err := rpprof.Lookup("goroutine").WriteTo(&b, 2)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lookup goroutine profile")
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "goroutines",
|
||||
Body: b.Bytes(),
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
@@ -4,13 +4,9 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
@@ -19,61 +15,86 @@ import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/config"
|
||||
)
|
||||
|
||||
const (
|
||||
cpuProfileDuration = 5 * time.Second
|
||||
)
|
||||
|
||||
func (a *App) GenerateSupportPacket(c request.CTX, options *model.SupportPacketOptions) []model.FileData {
|
||||
// If any errors we come across within this function, we will log it in a warning.txt file so that we know why certain files did not get produced if any
|
||||
var warnings []string
|
||||
|
||||
// Creating an array of files that we are going to be adding to our zip file
|
||||
fileDatas := []model.FileData{}
|
||||
|
||||
// A array of the functions that we can iterate through since they all have the same return value
|
||||
functions := map[string]func(c request.CTX) (*model.FileData, error){
|
||||
"support package": a.generateSupportPacketYaml,
|
||||
"plugins": a.createPluginsFile,
|
||||
"config": a.createSanitizedConfigFile,
|
||||
"cpu profile": a.createCPUProfile,
|
||||
"heap profile": a.createHeapProfile,
|
||||
"goroutines": a.createGoroutineProfile,
|
||||
"metadata": a.createSupportPacketMetadata,
|
||||
"support packet": a.generateSupportPacketYaml,
|
||||
"plugins": a.createPluginsFile,
|
||||
"config": a.createSanitizedConfigFile,
|
||||
"cpu profile": a.Srv().Platform().CreateCPUProfile,
|
||||
"heap profile": a.Srv().Platform().CreateHeapProfile,
|
||||
"goroutines": a.Srv().Platform().CreateGoroutineProfile,
|
||||
"metadata": a.createSupportPacketMetadata,
|
||||
}
|
||||
|
||||
if options.IncludeLogs {
|
||||
functions["mattermost log"] = a.GetMattermostLog
|
||||
functions["notification log"] = a.getNotificationsLog
|
||||
functions["mattermost log"] = a.Srv().Platform().GetLogFile
|
||||
functions["notification log"] = a.Srv().Platform().GetNotificationLogFile
|
||||
}
|
||||
|
||||
for name, fn := range functions {
|
||||
fileData, err := fn(c)
|
||||
if err != nil {
|
||||
c.Logger().Error("Failed to generate file for support package", mlog.Err(err), mlog.String("file", name))
|
||||
warnings = append(warnings, err.Error())
|
||||
}
|
||||
// If any errors we come across within this function, we will log it in a warning.txt file so that we know why certain files did not get produced if any
|
||||
var warnings *multierror.Error
|
||||
// Creating an array of files that we are going to be adding to our zip file
|
||||
var fileDatas []model.FileData
|
||||
var wg sync.WaitGroup
|
||||
var mut sync.Mutex // Protects warnings and fileDatas
|
||||
|
||||
if fileData != nil {
|
||||
fileDatas = append(fileDatas, *fileData)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for name, fn := range functions {
|
||||
fileData, err := fn(c)
|
||||
mut.Lock()
|
||||
if err != nil {
|
||||
c.Logger().Error("Failed to generate file for Support Packet", mlog.String("file", name), mlog.Err(err))
|
||||
warnings = multierror.Append(warnings, err)
|
||||
}
|
||||
|
||||
if fileData != nil {
|
||||
fileDatas = append(fileDatas, *fileData)
|
||||
}
|
||||
mut.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
// Run the cluster generation in a separate goroutine as CPU profile generation and file upload can take a long time
|
||||
if cluster := a.Cluster(); cluster != nil && *a.Config().ClusterSettings.Enable {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
files, err := cluster.GenerateSupportPacket(c, options)
|
||||
mut.Lock()
|
||||
if err != nil {
|
||||
c.Logger().Error("Failed to generate Support Packet from cluster nodes", mlog.Err(err))
|
||||
warnings = multierror.Append(warnings, err)
|
||||
}
|
||||
|
||||
for _, node := range files {
|
||||
fileDatas = append(fileDatas, node...)
|
||||
}
|
||||
mut.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||
pluginContext := pluginContext(c)
|
||||
for _, id := range options.PluginPackets {
|
||||
hooks, err := pluginsEnvironment.HooksForPlugin(id)
|
||||
if err != nil {
|
||||
c.Logger().Error("Failed to call hooks for plugin", mlog.Err(err), mlog.String("plugin", id))
|
||||
warnings = append(warnings, err.Error())
|
||||
warnings = multierror.Append(warnings, err)
|
||||
continue
|
||||
}
|
||||
pluginData, err := hooks.GenerateSupportData(pluginContext)
|
||||
if err != nil {
|
||||
c.Logger().Warn("Failed to generate plugin file for support package", mlog.Err(err), mlog.String("plugin", id))
|
||||
warnings = append(warnings, err.Error())
|
||||
c.Logger().Warn("Failed to generate plugin file for Support Packet", mlog.Err(err), mlog.String("plugin", id))
|
||||
warnings = multierror.Append(warnings, err)
|
||||
continue
|
||||
}
|
||||
for _, data := range pluginData {
|
||||
@@ -83,11 +104,10 @@ func (a *App) GenerateSupportPacket(c request.CTX, options *model.SupportPacketO
|
||||
}
|
||||
|
||||
// Adding a warning.txt file to the fileDatas if any warning
|
||||
if len(warnings) > 0 {
|
||||
finalWarning := strings.Join(warnings, "\n")
|
||||
if warnings != nil {
|
||||
fileDatas = append(fileDatas, model.FileData{
|
||||
Filename: "warning.txt",
|
||||
Body: []byte(finalWarning),
|
||||
Filename: model.SupportPacketErrorFile,
|
||||
Body: []byte(warnings.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,8 +128,8 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
|
||||
/* Cluster */
|
||||
|
||||
var clusterID string
|
||||
if a.Cluster() != nil {
|
||||
clusterID = a.Cluster().GetClusterId()
|
||||
if cluster := a.Cluster(); cluster != nil {
|
||||
clusterID = cluster.GetClusterId()
|
||||
}
|
||||
|
||||
/* File store */
|
||||
@@ -124,8 +144,7 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
|
||||
/* LDAP */
|
||||
|
||||
var vendorName, vendorVersion string
|
||||
ldap := a.Ldap()
|
||||
if ldap != nil {
|
||||
if ldap := a.Ldap(); ldap != nil {
|
||||
vendorName, vendorVersion, err = ldap.GetVendorNameAndVendorVersion(c)
|
||||
if err != nil {
|
||||
rErr = multierror.Append(errors.Wrap(err, "error while getting LDAP vendor info"))
|
||||
@@ -143,9 +162,9 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
|
||||
|
||||
var elasticServerVersion string
|
||||
var elasticServerPlugins []string
|
||||
if a.Srv().Platform().SearchEngine.ElasticsearchEngine != nil {
|
||||
elasticServerVersion = a.Srv().Platform().SearchEngine.ElasticsearchEngine.GetFullVersion()
|
||||
elasticServerPlugins = a.Srv().Platform().SearchEngine.ElasticsearchEngine.GetPlugins()
|
||||
if se := a.Srv().Platform().SearchEngine.ElasticsearchEngine; se != nil {
|
||||
elasticServerVersion = se.GetFullVersion()
|
||||
elasticServerPlugins = se.GetPlugins()
|
||||
}
|
||||
|
||||
/* License */
|
||||
@@ -182,19 +201,20 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
|
||||
analytics, appErr := a.GetAnalyticsForSupportPacket(c)
|
||||
if appErr != nil {
|
||||
rErr = multierror.Append(errors.Wrap(appErr, "error while getting analytics"))
|
||||
}
|
||||
if len(analytics) < 11 {
|
||||
rErr = multierror.Append(errors.New("not enought analytics information found"))
|
||||
} else {
|
||||
totalChannels = int(analytics[0].Value) + int(analytics[1].Value)
|
||||
totalPosts = int(analytics[2].Value)
|
||||
totalTeams = int(analytics[4].Value)
|
||||
websocketConnections = int(analytics[5].Value)
|
||||
masterDbConnections = int(analytics[6].Value)
|
||||
replicaDbConnections = int(analytics[7].Value)
|
||||
dailyActiveUsers = int(analytics[8].Value)
|
||||
monthlyActiveUsers = int(analytics[9].Value)
|
||||
inactiveUserCount = int(analytics[10].Value)
|
||||
if len(analytics) < 11 {
|
||||
rErr = multierror.Append(errors.New("not enough analytics information found"))
|
||||
} else {
|
||||
totalChannels = int(analytics[0].Value) + int(analytics[1].Value)
|
||||
totalPosts = int(analytics[2].Value)
|
||||
totalTeams = int(analytics[4].Value)
|
||||
websocketConnections = int(analytics[5].Value)
|
||||
masterDbConnections = int(analytics[6].Value)
|
||||
replicaDbConnections = int(analytics[7].Value)
|
||||
dailyActiveUsers = int(analytics[8].Value)
|
||||
monthlyActiveUsers = int(analytics[9].Value)
|
||||
inactiveUserCount = int(analytics[10].Value)
|
||||
}
|
||||
}
|
||||
|
||||
/* Jobs */
|
||||
@@ -228,7 +248,7 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
|
||||
rErr = multierror.Append(errors.Wrap(err, "error while getting migration jobs"))
|
||||
}
|
||||
|
||||
// Creating the struct for support packet yaml file
|
||||
// Creating the struct for Support Packet yaml file
|
||||
supportPacket := model.SupportPacket{
|
||||
/* Build information */
|
||||
ServerOS: runtime.GOOS,
|
||||
@@ -283,10 +303,10 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
|
||||
MigrationJobs: migrationJobs,
|
||||
}
|
||||
|
||||
// Marshal to a Yaml File
|
||||
// Marshal to a YAML File
|
||||
supportPacketYaml, err := yaml.Marshal(&supportPacket)
|
||||
if err != nil {
|
||||
rErr = multierror.Append(errors.Wrap(err, "failed to marshal support package into yaml"))
|
||||
rErr = multierror.Append(errors.Wrap(err, "failed to marshal Support Packet into yaml"))
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
@@ -296,61 +316,6 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
|
||||
return fileData, rErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (a *App) createPluginsFile(_ request.CTX) (*model.FileData, error) {
|
||||
// Getting the plugins installed on the server, prettify it, and then add them to the file data array
|
||||
pluginsResponse, appErr := a.GetPlugins()
|
||||
if appErr != nil {
|
||||
return nil, errors.Wrap(appErr, "failed to get plugin list for support package")
|
||||
}
|
||||
|
||||
pluginsPrettyJSON, err := json.MarshalIndent(pluginsResponse, "", " ")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal plugin list into json")
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "plugins.json",
|
||||
Body: pluginsPrettyJSON,
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
func (a *App) getNotificationsLog(_ request.CTX) (*model.FileData, error) {
|
||||
if !*a.Config().NotificationLogSettings.EnableFile {
|
||||
return nil, errors.New("Unable to retrieve notifications.log because LogSettings: EnableFile is set to false")
|
||||
}
|
||||
|
||||
notificationsLog := config.GetNotificationsLogFileLocation(*a.Config().LogSettings.FileLocation)
|
||||
notificationsLogFileData, err := os.ReadFile(notificationsLog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed read notifcation log file at path %s", notificationsLog)
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "notifications.log",
|
||||
Body: notificationsLogFileData,
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
func (a *App) GetMattermostLog(ctx request.CTX) (*model.FileData, error) {
|
||||
if !*a.Config().LogSettings.EnableFile {
|
||||
return nil, errors.New("Unable to retrieve mattermost.log because LogSettings: EnableFile is set to false")
|
||||
}
|
||||
|
||||
mattermostLog := config.GetLogFileLocation(*a.Config().LogSettings.FileLocation)
|
||||
mattermostLogFileData, err := os.ReadFile(mattermostLog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed read mattermost log file at path %s", mattermostLog)
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "mattermost.log",
|
||||
Body: mattermostLogFileData,
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
func (a *App) createSanitizedConfigFile(_ request.CTX) (*model.FileData, error) {
|
||||
// Getting sanitized config, prettifying it, and then adding it to our file data array
|
||||
sanitizedConfigPrettyJSON, err := json.MarshalIndent(a.GetSanitizedConfig(), "", " ")
|
||||
@@ -365,51 +330,21 @@ func (a *App) createSanitizedConfigFile(_ request.CTX) (*model.FileData, error)
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
func (a *App) createCPUProfile(_ request.CTX) (*model.FileData, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
err := pprof.StartCPUProfile(&b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to start CPU profile")
|
||||
func (a *App) createPluginsFile(_ request.CTX) (*model.FileData, error) {
|
||||
// Getting the plugins installed on the server, prettify it, and then add them to the file data array
|
||||
pluginsResponse, appErr := a.GetPlugins()
|
||||
if appErr != nil {
|
||||
return nil, errors.Wrap(appErr, "failed to get plugin list for Support Packet")
|
||||
}
|
||||
|
||||
time.Sleep(cpuProfileDuration)
|
||||
|
||||
pprof.StopCPUProfile()
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "cpu.prof",
|
||||
Body: b.Bytes(),
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
func (a *App) createHeapProfile(request.CTX) (*model.FileData, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
err := pprof.Lookup("heap").WriteTo(&b, 0)
|
||||
pluginsPrettyJSON, err := json.MarshalIndent(pluginsResponse, "", " ")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lookup heap profile")
|
||||
return nil, errors.Wrap(err, "failed to marshal plugin list into json")
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "heap.prof",
|
||||
Body: b.Bytes(),
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
|
||||
func (a *App) createGoroutineProfile(_ request.CTX) (*model.FileData, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
err := pprof.Lookup("goroutine").WriteTo(&b, 2)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lookup goroutine profile")
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
Filename: "goroutines",
|
||||
Body: b.Bytes(),
|
||||
Filename: "plugins.json",
|
||||
Body: pluginsPrettyJSON,
|
||||
}
|
||||
return fileData, nil
|
||||
}
|
||||
@@ -417,12 +352,12 @@ func (a *App) createGoroutineProfile(_ request.CTX) (*model.FileData, error) {
|
||||
func (a *App) createSupportPacketMetadata(_ request.CTX) (*model.FileData, error) {
|
||||
metadata, err := model.GeneratePacketMetadata(model.SupportPacketType, a.TelemetryId(), a.License(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate packet metadata")
|
||||
return nil, errors.Wrap(err, "failed to generate Packet metadata")
|
||||
}
|
||||
|
||||
b, err := yaml.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal packet metadata into yaml")
|
||||
return nil, errors.Wrap(err, "failed to marshal Packet metadata into yaml")
|
||||
}
|
||||
|
||||
fileData := &model.FileData{
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestCreatePluginsFile(t *testing.T) {
|
||||
// Plugins off in settings so no fileData and we get a warning instead
|
||||
fileData, err = th.App.createPluginsFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "failed to get plugin list for support package")
|
||||
assert.ErrorContains(t, err, "failed to get plugin list for Support Packet")
|
||||
}
|
||||
|
||||
func TestGenerateSupportPacketYaml(t *testing.T) {
|
||||
@@ -68,7 +68,7 @@ func TestGenerateSupportPacketYaml(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("Happy path", func(t *testing.T) {
|
||||
// Happy path where we have a support packet yaml file without any warnings
|
||||
// Happy path where we have a Support Packet yaml file without any warnings
|
||||
packet := generateSupportPacket(t)
|
||||
|
||||
/* Build information */
|
||||
@@ -207,7 +207,7 @@ func TestGenerateSupportPacket(t *testing.T) {
|
||||
}
|
||||
genMockLogFiles()
|
||||
|
||||
t.Run("generate support packet with logs", func(t *testing.T) {
|
||||
t.Run("generate Support Packet with logs", func(t *testing.T) {
|
||||
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
|
||||
IncludeLogs: true,
|
||||
})
|
||||
@@ -232,7 +232,7 @@ func TestGenerateSupportPacket(t *testing.T) {
|
||||
assert.ElementsMatch(t, testFiles, rFileNames)
|
||||
})
|
||||
|
||||
t.Run("generate support packet without logs", func(t *testing.T) {
|
||||
t.Run("generate Support Packet without logs", func(t *testing.T) {
|
||||
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
|
||||
IncludeLogs: false,
|
||||
})
|
||||
@@ -326,96 +326,6 @@ func TestGenerateSupportPacket(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetNotificationsLog(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
// Disable notifications file setting in config so we should get an warning
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.EnableFile = false
|
||||
})
|
||||
|
||||
fileData, err := th.App.getNotificationsLog(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "Unable to retrieve notifications.log because LogSettings: EnableFile is set to false")
|
||||
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(dir)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Enable notifications file but point to an empty directory to get an error trying to read the file
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.EnableFile = true
|
||||
*cfg.LogSettings.FileLocation = dir
|
||||
})
|
||||
|
||||
logLocation := config.GetNotificationsLogFileLocation(dir)
|
||||
|
||||
// There is no notifications.log file yet, so this fails
|
||||
fileData, err = th.App.getNotificationsLog(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "failed read notifcation log file at path "+logLocation)
|
||||
|
||||
// Happy path where we have file and no error
|
||||
d1 := []byte("hello\ngo\n")
|
||||
err = os.WriteFile(logLocation, d1, 0777)
|
||||
require.NoError(t, err)
|
||||
|
||||
fileData, err = th.App.getNotificationsLog(th.Context)
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, fileData)
|
||||
assert.Equal(t, "notifications.log", fileData.Filename)
|
||||
assert.Positive(t, len(fileData.Body))
|
||||
}
|
||||
|
||||
func TestGetMattermostLog(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
// disable mattermost log file setting in config so we should get an warning
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.EnableFile = false
|
||||
})
|
||||
|
||||
fileData, err := th.App.GetMattermostLog(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "Unable to retrieve mattermost.log because LogSettings: EnableFile is set to false")
|
||||
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(dir)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Enable log file but point to an empty directory to get an error trying to read the file
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.EnableFile = true
|
||||
*cfg.LogSettings.FileLocation = dir
|
||||
})
|
||||
|
||||
logLocation := config.GetLogFileLocation(dir)
|
||||
|
||||
// There is no mattermost.log file yet, so this fails
|
||||
fileData, err = th.App.GetMattermostLog(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.ErrorContains(t, err, "failed read mattermost log file at path "+logLocation)
|
||||
|
||||
// Happy path where we get a log file and no warning
|
||||
d1 := []byte("hello\ngo\n")
|
||||
err = os.WriteFile(logLocation, d1, 0777)
|
||||
require.NoError(t, err)
|
||||
|
||||
fileData, err = th.App.GetMattermostLog(th.Context)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, fileData)
|
||||
assert.Equal(t, "mattermost.log", fileData.Filename)
|
||||
assert.Positive(t, len(fileData.Body))
|
||||
}
|
||||
|
||||
func TestCreateSanitizedConfigFile(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
||||
)
|
||||
|
||||
@@ -63,6 +64,10 @@ func (c *FakeClusterInterface) QueryLogs(page, perPage int) (map[string][]string
|
||||
return make(map[string][]string), nil
|
||||
}
|
||||
|
||||
func (c *FakeClusterInterface) GenerateSupportPacket(rctx request.CTX, options *model.SupportPacketOptions) (map[string][]model.FileData, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *FakeClusterInterface) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func (s *MmctlE2ETestSuite) TestSupportPacketCmdF() {
|
||||
printer.SetFormat(printer.FormatPlain)
|
||||
s.T().Cleanup(func() { printer.SetFormat(printer.FormatJSON) })
|
||||
|
||||
s.Run("Download support packet with default filename", func() {
|
||||
s.Run("Download Support Packet with default filename", func() {
|
||||
printer.Clean()
|
||||
|
||||
s.T().Cleanup(cleanupSupportPacket(s.T()))
|
||||
@@ -141,7 +141,7 @@ func (s *MmctlE2ETestSuite) TestSupportPacketCmdF() {
|
||||
s.True(found)
|
||||
})
|
||||
|
||||
s.Run("Download support packet with custom filename", func() {
|
||||
s.Run("Download Support Packet with custom filename", func() {
|
||||
printer.Clean()
|
||||
|
||||
err := SystemSupportPacketCmd.ParseFlags([]string{"-o", "foo.zip"})
|
||||
|
||||
@@ -238,7 +238,7 @@ func (s *MmctlUnitTestSuite) TestSupportPacketCmdF() {
|
||||
printer.SetFormat(printer.FormatPlain)
|
||||
s.T().Cleanup(func() { printer.SetFormat(printer.FormatJSON) })
|
||||
|
||||
s.Run("Download support packet with default filename", func() {
|
||||
s.Run("Download Support Packet with default filename", func() {
|
||||
printer.Clean()
|
||||
|
||||
s.T().Cleanup(cleanupSupportPacket(s.T()))
|
||||
@@ -274,7 +274,7 @@ func (s *MmctlUnitTestSuite) TestSupportPacketCmdF() {
|
||||
s.True(found)
|
||||
})
|
||||
|
||||
s.Run("Download support packet with custom filename", func() {
|
||||
s.Run("Download Support Packet with custom filename", func() {
|
||||
printer.Clean()
|
||||
|
||||
data := []byte("some bytes")
|
||||
|
||||
@@ -133,6 +133,8 @@ services:
|
||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||
- "MM_LOGSETTINGS_FILELOCATION=./logs/leader"
|
||||
- "MM_NOTIFICATIONLOGSETTINGSS_FILELOCATION=./logs/leader"
|
||||
networks:
|
||||
- mm-test
|
||||
depends_on:
|
||||
@@ -169,6 +171,8 @@ services:
|
||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||
- "MM_LOGSETTINGS_FILELOCATION=./logs/follower"
|
||||
- "MM_NOTIFICATIONLOGSETTINGSS_FILELOCATION=./logs/follower"
|
||||
networks:
|
||||
- mm-test
|
||||
depends_on:
|
||||
@@ -205,6 +209,8 @@ services:
|
||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||
- "MM_LOGSETTINGS_FILELOCATION=./logs/follower2"
|
||||
- "MM_NOTIFICATIONLOGSETTINGSS_FILELOCATION=./logs/follower2"
|
||||
networks:
|
||||
- mm-test
|
||||
depends_on:
|
||||
|
||||
@@ -5,6 +5,7 @@ package einterfaces
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
)
|
||||
|
||||
type ClusterMessageHandler func(msg *model.ClusterMessage)
|
||||
@@ -27,6 +28,7 @@ type ClusterInterface interface {
|
||||
GetClusterStats() ([]*model.ClusterStats, *model.AppError)
|
||||
GetLogs(page, perPage int) ([]string, *model.AppError)
|
||||
QueryLogs(page, perPage int) (map[string][]string, *model.AppError)
|
||||
GenerateSupportPacket(rctx request.CTX, options *model.SupportPacketOptions) (map[string][]model.FileData, error)
|
||||
GetPluginStatuses() (model.PluginStatuses, *model.AppError)
|
||||
ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError
|
||||
// WebConnCountForUser returns the number of active webconn connections
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
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
|
||||
@@ -36,6 +38,36 @@ func (_m *ClusterInterface) ConfigChanged(previousConfig *model.Config, newConfi
|
||||
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()
|
||||
|
||||
@@ -4636,7 +4636,7 @@ func (c *Client4) GetFileInfosForPostIncludeDeleted(ctx context.Context, postId
|
||||
|
||||
// General/System Section
|
||||
|
||||
// GenerateSupportPacket downloads the generated support packet
|
||||
// GenerateSupportPacket generates and downloads a Support Packet.
|
||||
func (c *Client4) GenerateSupportPacket(ctx context.Context) ([]byte, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.systemRoute()+"/support_packet", "")
|
||||
if err != nil {
|
||||
|
||||
@@ -45,16 +45,18 @@ const (
|
||||
// m.ClusterEventMap in metrics/metrics.go file.
|
||||
|
||||
// Gossip communication
|
||||
ClusterGossipEventRequestGetLogs = "gossip_request_get_logs"
|
||||
ClusterGossipEventResponseGetLogs = "gossip_response_get_logs"
|
||||
ClusterGossipEventRequestGetClusterStats = "gossip_request_cluster_stats"
|
||||
ClusterGossipEventResponseGetClusterStats = "gossip_response_cluster_stats"
|
||||
ClusterGossipEventRequestGetPluginStatuses = "gossip_request_plugin_statuses"
|
||||
ClusterGossipEventResponseGetPluginStatuses = "gossip_response_plugin_statuses"
|
||||
ClusterGossipEventRequestSaveConfig = "gossip_request_save_config"
|
||||
ClusterGossipEventResponseSaveConfig = "gossip_response_save_config"
|
||||
ClusterGossipEventRequestWebConnCount = "gossip_request_webconn_count"
|
||||
ClusterGossipEventResponseWebConnCount = "gossip_response_webconn_count"
|
||||
ClusterGossipEventRequestGetLogs = "gossip_request_get_logs"
|
||||
ClusterGossipEventResponseGetLogs = "gossip_response_get_logs"
|
||||
ClusterGossipEventRequestGenerateSupportPacket = "gossip_request_generate_support_packet"
|
||||
ClusterGossipEventResponseGenerateSupportPacket = "gossip_response_generate_support_packet"
|
||||
ClusterGossipEventRequestGetClusterStats = "gossip_request_cluster_stats"
|
||||
ClusterGossipEventResponseGetClusterStats = "gossip_response_cluster_stats"
|
||||
ClusterGossipEventRequestGetPluginStatuses = "gossip_request_plugin_statuses"
|
||||
ClusterGossipEventResponseGetPluginStatuses = "gossip_response_plugin_statuses"
|
||||
ClusterGossipEventRequestSaveConfig = "gossip_request_save_config"
|
||||
ClusterGossipEventResponseSaveConfig = "gossip_response_save_config"
|
||||
ClusterGossipEventRequestWebConnCount = "gossip_request_webconn_count"
|
||||
ClusterGossipEventResponseWebConnCount = "gossip_response_webconn_count"
|
||||
|
||||
// SendTypes for ClusterMessage.
|
||||
ClusterSendBestEffort = "best_effort"
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
SupportPacketErrorFile = "warning.txt"
|
||||
)
|
||||
|
||||
type SupportPacket struct {
|
||||
/* Build information */
|
||||
|
||||
|
||||
@@ -3310,7 +3310,7 @@
|
||||
"combined_system_message.removed_from_team.one_you": "You were **removed from the team**.",
|
||||
"combined_system_message.removed_from_team.two": "{firstUser} and {secondUser} were **removed from the team**.",
|
||||
"combined_system_message.you": "You",
|
||||
"commercial_support.description": "If you're experiencing issues, [submit a support ticket](!{supportLink}). To help with troubleshooting, it's recommended to download the support packet below that includes more details about your Mattermost environment.",
|
||||
"commercial_support.description": "If you're experiencing issues, [submit a support ticket](!{supportLink}). To help with troubleshooting, it's recommended to download the Support Packet below that includes more details about your Mattermost environment.",
|
||||
"commercial_support.download_contents": "**Select your Support Packet contents to download**",
|
||||
"commercial_support.download_support_packet": "Download Support Packet",
|
||||
"commercial_support.title": "Commercial Support",
|
||||
|
||||
Reference in New Issue
Block a user