[MM-54593] HA aware Support Packet (#27598)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3f4b8e8137
Коммит
1158e6358c
@@ -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{
|
||||
|
||||
Ссылка в новой задаче
Block a user