[MM-54593] HA aware Support Packet (#27598)

Этот коммит содержится в:
Ben Schumacher
2024-08-03 16:11:13 +02:00
коммит произвёл GitHub
родитель 3f4b8e8137
Коммит 1158e6358c
24 изменённых файлов: 377 добавлений и 300 удалений

Просмотреть файл

@@ -1138,7 +1138,7 @@
- name: plugin_packets - name: plugin_packets
in: query in: query
description: | 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 __Minimum server version__: 9.8.0
required: false required: false

Просмотреть файл

@@ -49,6 +49,6 @@ const goToSupportPacketGenerationModal = () => {
cy.findByRole('button', {name: 'Menu Icon'}).should('exist').click(); cy.findByRole('button', {name: 'Menu Icon'}).should('exist').click();
cy.findByRole('button', {name: 'Commercial Support dialog'}).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'); cy.get('a.DownloadSupportPacket').should('exist');
}; };

Просмотреть файл

@@ -20,6 +20,7 @@ import (
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/channels/audit" "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/cache"
"github.com/mattermost/mattermost/server/v8/platform/services/upgrader" "github.com/mattermost/mattermost/server/v8/platform/services/upgrader"
"github.com/mattermost/mattermost/server/v8/platform/shared/web" "github.com/mattermost/mattermost/server/v8/platform/shared/web"
@@ -84,7 +85,7 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
return 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) { if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem) c.SetPermissionError(model.PermissionManageSystem)
return return
@@ -427,14 +428,14 @@ func downloadLogs(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
fileData, err := c.App.GetMattermostLog(c.AppContext) fileData, err := c.App.Srv().Platform().GetLogFile(c.AppContext)
if err != nil { if err != nil {
c.Err = model.NewAppError("downloadLogs", "api.system.logs.download_bytes_buffer.app_error", nil, err.Error(), http.StatusInternalServerError) c.Err = model.NewAppError("downloadLogs", "api.system.logs.download_bytes_buffer.app_error", nil, err.Error(), http.StatusInternalServerError)
return return
} }
reader := bytes.NewReader(fileData.Body) reader := bytes.NewReader(fileData.Body)
web.WriteFileResponse("mattermost.log", web.WriteFileResponse(config.LogFilename,
"text/plain", "text/plain",
int64(len(fileData.Body)), int64(len(fileData.Body)),
time.Now(), time.Now(),

Просмотреть файл

@@ -211,7 +211,7 @@ func TestGenerateSupportPacket(t *testing.T) {
th.LoginSystemManager() th.LoginSystemManager()
defer th.TearDown() 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() l := model.NewTestLicense()
th.App.Srv().SetLicense(l) th.App.Srv().SetLicense(l)

Просмотреть файл

@@ -33,7 +33,7 @@ func (a *App) getAnalytics(rctx request.CTX, name string, teamID string, forSupp
} }
skipIntensiveQueries := false skipIntensiveQueries := false
// When generating a Support packet, always run intensive queries. // When generating a Support Packet, always run intensive queries.
if !forSupportPacket { if !forSupportPacket {
if systemUserCount > int64(*a.Config().AnalyticsSettings.MaxUsersForStatistics) { 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)) 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) GetLatestVersion(rctx request.CTX, latestVersionUrl string) (*model.GithubReleaseInfo, *model.AppError)
GetLogs(rctx request.CTX, page, perPage int) ([]string, *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) 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) GetMemberCountsByGroup(rctx request.CTX, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, *model.AppError)
GetMessageForNotification(post *model.Post, teamName, siteUrl string, translateFunc i18n.TranslateFunc) string GetMessageForNotification(post *model.Post, teamName, siteUrl string, translateFunc i18n.TranslateFunc) string
GetMultipleEmojiByName(c request.CTX, names []string) ([]*model.Emoji, *model.AppError) GetMultipleEmojiByName(c request.CTX, names []string) ([]*model.Emoji, *model.AppError)

Просмотреть файл

@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/einterfaces" "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) { func (c *ClusterMock) QueryLogs(page, perPage int) (map[string][]string, *model.AppError) {
return nil, nil 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) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { return nil, nil }
func (c *ClusterMock) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError { func (c *ClusterMock) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError {
return nil return nil

Просмотреть файл

@@ -7660,28 +7660,6 @@ func (a *OpenTracingAppLayer) GetMarketplacePlugins(rctx request.CTX, filter *mo
return resultVar0, resultVar1 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) { func (a *OpenTracingAppLayer) GetMemberCountsByGroup(rctx request.CTX, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, *model.AppError) {
origCtx := a.ctx origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetMemberCountsByGroup") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetMemberCountsByGroup")

Просмотреть файл

@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/einterfaces" "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) { func (c *ClusterMock) QueryLogs(page, perPage int) (map[string][]string, *model.AppError) {
return nil, nil 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) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { return nil, nil }
func (c *ClusterMock) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError { func (c *ClusterMock) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError {
return nil return nil

Просмотреть файл

@@ -6,14 +6,16 @@ package platform
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"io" "io"
"net/http" "net/http"
"os" "os"
"time" "time"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/config" "github.com/mattermost/mattermost/server/v8/config"
) )
@@ -206,6 +208,40 @@ func (ps *PlatformService) GetLogsSkipSend(page, perPage int, logFilter *model.L
return lines, nil 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 { func isLogFilteredByLevel(logFilter *model.LogFilter, entry *model.LogEntry) bool {
logLevels := logFilter.LogLevels logLevels := logFilter.LogLevels
if len(logLevels) == 0 { if len(logLevels) == 0 {

104
server/channels/app/platform/log_test.go Обычный файл
Просмотреть файл

@@ -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 package platform
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"net" "net"
@@ -11,6 +12,7 @@ import (
"net/http/pprof" "net/http/pprof"
"path" "path"
"runtime" "runtime"
rpprof "runtime/pprof"
"strings" "strings"
"sync" "sync"
"text/template" "text/template"
@@ -23,11 +25,15 @@ import (
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/shared/mlog" "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/channels/utils"
"github.com/mattermost/mattermost/server/v8/einterfaces" "github.com/mattermost/mattermost/server/v8/einterfaces"
) )
const TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second const (
TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second
cpuProfileDuration = 5 * time.Second
)
type platformMetrics struct { type platformMetrics struct {
server *http.Server server *http.Server
@@ -247,3 +253,52 @@ func (ps *PlatformService) Metrics() einterfaces.MetricsInterface {
return ps.metricsIFace 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 package app
import ( import (
"bytes"
"encoding/json" "encoding/json"
"os"
"runtime" "runtime"
"runtime/pprof" "sync"
"strings"
"time"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -19,61 +15,86 @@ import (
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request" "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 { 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 // 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){ functions := map[string]func(c request.CTX) (*model.FileData, error){
"support package": a.generateSupportPacketYaml, "support packet": a.generateSupportPacketYaml,
"plugins": a.createPluginsFile, "plugins": a.createPluginsFile,
"config": a.createSanitizedConfigFile, "config": a.createSanitizedConfigFile,
"cpu profile": a.createCPUProfile, "cpu profile": a.Srv().Platform().CreateCPUProfile,
"heap profile": a.createHeapProfile, "heap profile": a.Srv().Platform().CreateHeapProfile,
"goroutines": a.createGoroutineProfile, "goroutines": a.Srv().Platform().CreateGoroutineProfile,
"metadata": a.createSupportPacketMetadata, "metadata": a.createSupportPacketMetadata,
} }
if options.IncludeLogs { if options.IncludeLogs {
functions["mattermost log"] = a.GetMattermostLog functions["mattermost log"] = a.Srv().Platform().GetLogFile
functions["notification log"] = a.getNotificationsLog functions["notification log"] = a.Srv().Platform().GetNotificationLogFile
} }
for name, fn := range functions { // 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
fileData, err := fn(c) var warnings *multierror.Error
if err != nil { // Creating an array of files that we are going to be adding to our zip file
c.Logger().Error("Failed to generate file for support package", mlog.Err(err), mlog.String("file", name)) var fileDatas []model.FileData
warnings = append(warnings, err.Error()) var wg sync.WaitGroup
} var mut sync.Mutex // Protects warnings and fileDatas
if fileData != nil { wg.Add(1)
fileDatas = append(fileDatas, *fileData) 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 { if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
pluginContext := pluginContext(c) pluginContext := pluginContext(c)
for _, id := range options.PluginPackets { for _, id := range options.PluginPackets {
hooks, err := pluginsEnvironment.HooksForPlugin(id) hooks, err := pluginsEnvironment.HooksForPlugin(id)
if err != nil { if err != nil {
c.Logger().Error("Failed to call hooks for plugin", mlog.Err(err), mlog.String("plugin", id)) 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 continue
} }
pluginData, err := hooks.GenerateSupportData(pluginContext) pluginData, err := hooks.GenerateSupportData(pluginContext)
if err != nil { if err != nil {
c.Logger().Warn("Failed to generate plugin file for support package", mlog.Err(err), mlog.String("plugin", id)) c.Logger().Warn("Failed to generate plugin file for Support Packet", mlog.Err(err), mlog.String("plugin", id))
warnings = append(warnings, err.Error()) warnings = multierror.Append(warnings, err)
continue continue
} }
for _, data := range pluginData { 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 // Adding a warning.txt file to the fileDatas if any warning
if len(warnings) > 0 { if warnings != nil {
finalWarning := strings.Join(warnings, "\n")
fileDatas = append(fileDatas, model.FileData{ fileDatas = append(fileDatas, model.FileData{
Filename: "warning.txt", Filename: model.SupportPacketErrorFile,
Body: []byte(finalWarning), Body: []byte(warnings.Error()),
}) })
} }
@@ -108,8 +128,8 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
/* Cluster */ /* Cluster */
var clusterID string var clusterID string
if a.Cluster() != nil { if cluster := a.Cluster(); cluster != nil {
clusterID = a.Cluster().GetClusterId() clusterID = cluster.GetClusterId()
} }
/* File store */ /* File store */
@@ -124,8 +144,7 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
/* LDAP */ /* LDAP */
var vendorName, vendorVersion string var vendorName, vendorVersion string
ldap := a.Ldap() if ldap := a.Ldap(); ldap != nil {
if ldap != nil {
vendorName, vendorVersion, err = ldap.GetVendorNameAndVendorVersion(c) vendorName, vendorVersion, err = ldap.GetVendorNameAndVendorVersion(c)
if err != nil { if err != nil {
rErr = multierror.Append(errors.Wrap(err, "error while getting LDAP vendor info")) 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 elasticServerVersion string
var elasticServerPlugins []string var elasticServerPlugins []string
if a.Srv().Platform().SearchEngine.ElasticsearchEngine != nil { if se := a.Srv().Platform().SearchEngine.ElasticsearchEngine; se != nil {
elasticServerVersion = a.Srv().Platform().SearchEngine.ElasticsearchEngine.GetFullVersion() elasticServerVersion = se.GetFullVersion()
elasticServerPlugins = a.Srv().Platform().SearchEngine.ElasticsearchEngine.GetPlugins() elasticServerPlugins = se.GetPlugins()
} }
/* License */ /* License */
@@ -182,19 +201,20 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
analytics, appErr := a.GetAnalyticsForSupportPacket(c) analytics, appErr := a.GetAnalyticsForSupportPacket(c)
if appErr != nil { if appErr != nil {
rErr = multierror.Append(errors.Wrap(appErr, "error while getting analytics")) 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 { } else {
totalChannels = int(analytics[0].Value) + int(analytics[1].Value) if len(analytics) < 11 {
totalPosts = int(analytics[2].Value) rErr = multierror.Append(errors.New("not enough analytics information found"))
totalTeams = int(analytics[4].Value) } else {
websocketConnections = int(analytics[5].Value) totalChannels = int(analytics[0].Value) + int(analytics[1].Value)
masterDbConnections = int(analytics[6].Value) totalPosts = int(analytics[2].Value)
replicaDbConnections = int(analytics[7].Value) totalTeams = int(analytics[4].Value)
dailyActiveUsers = int(analytics[8].Value) websocketConnections = int(analytics[5].Value)
monthlyActiveUsers = int(analytics[9].Value) masterDbConnections = int(analytics[6].Value)
inactiveUserCount = int(analytics[10].Value) replicaDbConnections = int(analytics[7].Value)
dailyActiveUsers = int(analytics[8].Value)
monthlyActiveUsers = int(analytics[9].Value)
inactiveUserCount = int(analytics[10].Value)
}
} }
/* Jobs */ /* 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")) 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{ supportPacket := model.SupportPacket{
/* Build information */ /* Build information */
ServerOS: runtime.GOOS, ServerOS: runtime.GOOS,
@@ -283,10 +303,10 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
MigrationJobs: migrationJobs, MigrationJobs: migrationJobs,
} }
// Marshal to a Yaml File // Marshal to a YAML File
supportPacketYaml, err := yaml.Marshal(&supportPacket) supportPacketYaml, err := yaml.Marshal(&supportPacket)
if err != nil { 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{ fileData := &model.FileData{
@@ -296,61 +316,6 @@ func (a *App) generateSupportPacketYaml(c request.CTX) (*model.FileData, error)
return fileData, rErr.ErrorOrNil() 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) { func (a *App) createSanitizedConfigFile(_ request.CTX) (*model.FileData, error) {
// Getting sanitized config, prettifying it, and then adding it to our file data array // Getting sanitized config, prettifying it, and then adding it to our file data array
sanitizedConfigPrettyJSON, err := json.MarshalIndent(a.GetSanitizedConfig(), "", " ") sanitizedConfigPrettyJSON, err := json.MarshalIndent(a.GetSanitizedConfig(), "", " ")
@@ -365,51 +330,21 @@ func (a *App) createSanitizedConfigFile(_ request.CTX) (*model.FileData, error)
return fileData, nil return fileData, nil
} }
func (a *App) createCPUProfile(_ request.CTX) (*model.FileData, error) { func (a *App) createPluginsFile(_ request.CTX) (*model.FileData, error) {
var b bytes.Buffer // Getting the plugins installed on the server, prettify it, and then add them to the file data array
pluginsResponse, appErr := a.GetPlugins()
err := pprof.StartCPUProfile(&b) if appErr != nil {
if err != nil { return nil, errors.Wrap(appErr, "failed to get plugin list for Support Packet")
return nil, errors.Wrap(err, "failed to start CPU profile")
} }
time.Sleep(cpuProfileDuration) pluginsPrettyJSON, err := json.MarshalIndent(pluginsResponse, "", " ")
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)
if err != nil { 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{ fileData := &model.FileData{
Filename: "heap.prof", Filename: "plugins.json",
Body: b.Bytes(), Body: pluginsPrettyJSON,
}
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(),
} }
return fileData, nil 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) { func (a *App) createSupportPacketMetadata(_ request.CTX) (*model.FileData, error) {
metadata, err := model.GeneratePacketMetadata(model.SupportPacketType, a.TelemetryId(), a.License(), nil) metadata, err := model.GeneratePacketMetadata(model.SupportPacketType, a.TelemetryId(), a.License(), nil)
if err != 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) b, err := yaml.Marshal(metadata)
if err != nil { 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{ 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 // Plugins off in settings so no fileData and we get a warning instead
fileData, err = th.App.createPluginsFile(th.Context) fileData, err = th.App.createPluginsFile(th.Context)
assert.Nil(t, fileData) 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) { func TestGenerateSupportPacketYaml(t *testing.T) {
@@ -68,7 +68,7 @@ func TestGenerateSupportPacketYaml(t *testing.T) {
} }
t.Run("Happy path", func(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) packet := generateSupportPacket(t)
/* Build information */ /* Build information */
@@ -207,7 +207,7 @@ func TestGenerateSupportPacket(t *testing.T) {
} }
genMockLogFiles() 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{ fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: true, IncludeLogs: true,
}) })
@@ -232,7 +232,7 @@ func TestGenerateSupportPacket(t *testing.T) {
assert.ElementsMatch(t, testFiles, rFileNames) 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{ fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false, 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) { func TestCreateSanitizedConfigFile(t *testing.T) {
th := Setup(t) th := Setup(t)
defer th.TearDown() defer th.TearDown()

Просмотреть файл

@@ -7,6 +7,7 @@ import (
"sync" "sync"
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/einterfaces" "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 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 { func (c *FakeClusterInterface) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError {
return nil return nil
} }

Просмотреть файл

@@ -112,7 +112,7 @@ func (s *MmctlE2ETestSuite) TestSupportPacketCmdF() {
printer.SetFormat(printer.FormatPlain) printer.SetFormat(printer.FormatPlain)
s.T().Cleanup(func() { printer.SetFormat(printer.FormatJSON) }) 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() printer.Clean()
s.T().Cleanup(cleanupSupportPacket(s.T())) s.T().Cleanup(cleanupSupportPacket(s.T()))
@@ -141,7 +141,7 @@ func (s *MmctlE2ETestSuite) TestSupportPacketCmdF() {
s.True(found) s.True(found)
}) })
s.Run("Download support packet with custom filename", func() { s.Run("Download Support Packet with custom filename", func() {
printer.Clean() printer.Clean()
err := SystemSupportPacketCmd.ParseFlags([]string{"-o", "foo.zip"}) err := SystemSupportPacketCmd.ParseFlags([]string{"-o", "foo.zip"})

Просмотреть файл

@@ -238,7 +238,7 @@ func (s *MmctlUnitTestSuite) TestSupportPacketCmdF() {
printer.SetFormat(printer.FormatPlain) printer.SetFormat(printer.FormatPlain)
s.T().Cleanup(func() { printer.SetFormat(printer.FormatJSON) }) 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() printer.Clean()
s.T().Cleanup(cleanupSupportPacket(s.T())) s.T().Cleanup(cleanupSupportPacket(s.T()))
@@ -274,7 +274,7 @@ func (s *MmctlUnitTestSuite) TestSupportPacketCmdF() {
s.True(found) s.True(found)
}) })
s.Run("Download support packet with custom filename", func() { s.Run("Download Support Packet with custom filename", func() {
printer.Clean() printer.Clean()
data := []byte("some bytes") data := []byte("some bytes")

Просмотреть файл

@@ -133,6 +133,8 @@ services:
- "RUN_SERVER_IN_BACKGROUND=false" - "RUN_SERVER_IN_BACKGROUND=false"
- "MM_CLUSTERSETTINGS_ENABLE=true" - "MM_CLUSTERSETTINGS_ENABLE=true"
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster" - "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
- "MM_LOGSETTINGS_FILELOCATION=./logs/leader"
- "MM_NOTIFICATIONLOGSETTINGSS_FILELOCATION=./logs/leader"
networks: networks:
- mm-test - mm-test
depends_on: depends_on:
@@ -169,6 +171,8 @@ services:
- "RUN_SERVER_IN_BACKGROUND=false" - "RUN_SERVER_IN_BACKGROUND=false"
- "MM_CLUSTERSETTINGS_ENABLE=true" - "MM_CLUSTERSETTINGS_ENABLE=true"
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster" - "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
- "MM_LOGSETTINGS_FILELOCATION=./logs/follower"
- "MM_NOTIFICATIONLOGSETTINGSS_FILELOCATION=./logs/follower"
networks: networks:
- mm-test - mm-test
depends_on: depends_on:
@@ -205,6 +209,8 @@ services:
- "RUN_SERVER_IN_BACKGROUND=false" - "RUN_SERVER_IN_BACKGROUND=false"
- "MM_CLUSTERSETTINGS_ENABLE=true" - "MM_CLUSTERSETTINGS_ENABLE=true"
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster" - "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
- "MM_LOGSETTINGS_FILELOCATION=./logs/follower2"
- "MM_NOTIFICATIONLOGSETTINGSS_FILELOCATION=./logs/follower2"
networks: networks:
- mm-test - mm-test
depends_on: depends_on:

Просмотреть файл

@@ -5,6 +5,7 @@ package einterfaces
import ( import (
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
) )
type ClusterMessageHandler func(msg *model.ClusterMessage) type ClusterMessageHandler func(msg *model.ClusterMessage)
@@ -27,6 +28,7 @@ type ClusterInterface interface {
GetClusterStats() ([]*model.ClusterStats, *model.AppError) GetClusterStats() ([]*model.ClusterStats, *model.AppError)
GetLogs(page, perPage int) ([]string, *model.AppError) GetLogs(page, perPage int) ([]string, *model.AppError)
QueryLogs(page, perPage int) (map[string][]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) GetPluginStatuses() (model.PluginStatuses, *model.AppError)
ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError
// WebConnCountForUser returns the number of active webconn connections // WebConnCountForUser returns the number of active webconn connections

Просмотреть файл

@@ -9,6 +9,8 @@ import (
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
model "github.com/mattermost/mattermost/server/public/model" 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 // ClusterInterface is an autogenerated mock type for the ClusterInterface type
@@ -36,6 +38,36 @@ func (_m *ClusterInterface) ConfigChanged(previousConfig *model.Config, newConfi
return r0 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: // GetClusterId provides a mock function with given fields:
func (_m *ClusterInterface) GetClusterId() string { func (_m *ClusterInterface) GetClusterId() string {
ret := _m.Called() ret := _m.Called()

Просмотреть файл

@@ -4636,7 +4636,7 @@ func (c *Client4) GetFileInfosForPostIncludeDeleted(ctx context.Context, postId
// General/System Section // 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) { func (c *Client4) GenerateSupportPacket(ctx context.Context) ([]byte, *Response, error) {
r, err := c.DoAPIGet(ctx, c.systemRoute()+"/support_packet", "") r, err := c.DoAPIGet(ctx, c.systemRoute()+"/support_packet", "")
if err != nil { if err != nil {

Просмотреть файл

@@ -45,16 +45,18 @@ const (
// m.ClusterEventMap in metrics/metrics.go file. // m.ClusterEventMap in metrics/metrics.go file.
// Gossip communication // Gossip communication
ClusterGossipEventRequestGetLogs = "gossip_request_get_logs" ClusterGossipEventRequestGetLogs = "gossip_request_get_logs"
ClusterGossipEventResponseGetLogs = "gossip_response_get_logs" ClusterGossipEventResponseGetLogs = "gossip_response_get_logs"
ClusterGossipEventRequestGetClusterStats = "gossip_request_cluster_stats" ClusterGossipEventRequestGenerateSupportPacket = "gossip_request_generate_support_packet"
ClusterGossipEventResponseGetClusterStats = "gossip_response_cluster_stats" ClusterGossipEventResponseGenerateSupportPacket = "gossip_response_generate_support_packet"
ClusterGossipEventRequestGetPluginStatuses = "gossip_request_plugin_statuses" ClusterGossipEventRequestGetClusterStats = "gossip_request_cluster_stats"
ClusterGossipEventResponseGetPluginStatuses = "gossip_response_plugin_statuses" ClusterGossipEventResponseGetClusterStats = "gossip_response_cluster_stats"
ClusterGossipEventRequestSaveConfig = "gossip_request_save_config" ClusterGossipEventRequestGetPluginStatuses = "gossip_request_plugin_statuses"
ClusterGossipEventResponseSaveConfig = "gossip_response_save_config" ClusterGossipEventResponseGetPluginStatuses = "gossip_response_plugin_statuses"
ClusterGossipEventRequestWebConnCount = "gossip_request_webconn_count" ClusterGossipEventRequestSaveConfig = "gossip_request_save_config"
ClusterGossipEventResponseWebConnCount = "gossip_response_webconn_count" ClusterGossipEventResponseSaveConfig = "gossip_response_save_config"
ClusterGossipEventRequestWebConnCount = "gossip_request_webconn_count"
ClusterGossipEventResponseWebConnCount = "gossip_response_webconn_count"
// SendTypes for ClusterMessage. // SendTypes for ClusterMessage.
ClusterSendBestEffort = "best_effort" ClusterSendBestEffort = "best_effort"

Просмотреть файл

@@ -8,6 +8,10 @@ import (
"io" "io"
) )
const (
SupportPacketErrorFile = "warning.txt"
)
type SupportPacket struct { type SupportPacket struct {
/* Build information */ /* 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.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.removed_from_team.two": "{firstUser} and {secondUser} were **removed from the team**.",
"combined_system_message.you": "You", "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_contents": "**Select your Support Packet contents to download**",
"commercial_support.download_support_packet": "Download Support Packet", "commercial_support.download_support_packet": "Download Support Packet",
"commercial_support.title": "Commercial Support", "commercial_support.title": "Commercial Support",