From b2f36c7cdff24c1200448db86b5116d5d8147cf4 Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Wed, 6 Sep 2023 09:21:25 +0200 Subject: [PATCH] [MM-54318] Add file storage information to support package (#24474) * Reorder stats in support package struct * Add file storage information to support package * Add clusterID * Change method name to DriverName * Ordering * Fix test --- server/channels/app/support_packet.go | 96 +++++++++++++------ server/channels/app/support_packet_test.go | 49 ++++++++-- .../platform/shared/filestore/filesstore.go | 1 + .../platform/shared/filestore/localstore.go | 4 + .../shared/filestore/mocks/FileBackend.go | 14 +++ server/platform/shared/filestore/s3store.go | 4 + server/public/model/system.go | 75 ++++++++++----- 7 files changed, 182 insertions(+), 61 deletions(-) diff --git a/server/channels/app/support_packet.go b/server/channels/app/support_packet.go index 09445be532..f75e9b2554 100644 --- a/server/channels/app/support_packet.go +++ b/server/channels/app/support_packet.go @@ -59,7 +59,36 @@ func (a *App) GenerateSupportPacket() []model.FileData { func (a *App) generateSupportPacketYaml() (*model.FileData, error) { var rErr error - // Here we are getting information regarding Elastic Search + /* DB */ + + databaseType, databaseSchemaVersion := a.Srv().DatabaseTypeAndSchemaVersion() + databaseVersion, _ := a.Srv().Store().GetDbVersion(false) + + /* Cluster */ + + var clusterID string + if a.Cluster() != nil { + clusterID = a.Cluster().GetClusterId() + } + + /* File store */ + + fileDriver := a.Srv().Platform().FileBackend().DriverName() + fileStatus := model.StatusOk + err := a.Srv().Platform().FileBackend().TestConnection() + if err != nil { + fileStatus = model.StatusFail + ": " + err.Error() + } + + /* LDAP */ + + var vendorName, vendorVersion string + if ldapInterface := a.ch.Ldap; a.ch.Ldap != nil { + vendorName, vendorVersion = ldapInterface.GetVendorNameAndVendorVersion() + } + + /* Elastic Search */ + var elasticServerVersion string var elasticServerPlugins []string if a.Srv().Platform().SearchEngine.ElasticsearchEngine != nil { @@ -67,16 +96,16 @@ func (a *App) generateSupportPacketYaml() (*model.FileData, error) { elasticServerPlugins = a.Srv().Platform().SearchEngine.ElasticsearchEngine.GetPlugins() } - // Here we are getting information regarding LDAP - ldapInterface := a.ch.Ldap - var vendorName, vendorVersion string - if ldapInterface != nil { - vendorName, vendorVersion = ldapInterface.GetVendorNameAndVendorVersion() + /* License */ + + licenseTo := "" + supportedUsers := 0 + if license := a.Srv().License(); license != nil { + supportedUsers = *license.Features.Users + licenseTo = license.Customer.Company } - // Here we are getting information regarding the database (mysql/postgres + current schema version) - databaseType, databaseSchemaVersion := a.Srv().DatabaseTypeAndSchemaVersion() - databaseVersion, _ := a.Srv().Store().GetDbVersion(false) + /* Jobs */ uniqueUserCount, err := a.Srv().Store().User().Count(model.UserCountOptions{}) if err != nil { @@ -112,31 +141,42 @@ func (a *App) generateSupportPacketYaml() (*model.FileData, error) { rErr = multierror.Append(errors.Wrap(err, "error while getting migration jobs")) } - licenseTo := "" - supportedUsers := 0 - if license := a.Srv().License(); license != nil { - supportedUsers = *license.Features.Users - licenseTo = license.Customer.Company - } - // Creating the struct for support packet yaml file supportPacket := model.SupportPacket{ - LicenseTo: licenseTo, - ServerOS: runtime.GOOS, - ServerArchitecture: runtime.GOARCH, - ServerVersion: model.CurrentVersion, - BuildHash: model.BuildHash, + /* Build information */ + ServerOS: runtime.GOOS, + ServerArchitecture: runtime.GOARCH, + ServerVersion: model.CurrentVersion, + BuildHash: model.BuildHash, + + /* DB */ DatabaseType: databaseType, DatabaseVersion: databaseVersion, DatabaseSchemaVersion: databaseSchemaVersion, - LdapVendorName: vendorName, - LdapVendorVersion: vendorVersion, - ElasticServerVersion: elasticServerVersion, - ElasticServerPlugins: elasticServerPlugins, - ActiveUsers: int(uniqueUserCount), + + /* Cluster */ + ClusterID: clusterID, + + /* File store */ + FileDriver: fileDriver, + FileStatus: fileStatus, + + /* LDAP */ + LdapVendorName: vendorName, + LdapVendorVersion: vendorVersion, + + /* Elastic Search */ + ElasticServerVersion: elasticServerVersion, + ElasticServerPlugins: elasticServerPlugins, + + /* License */ + LicenseTo: licenseTo, LicenseSupportedUsers: supportedUsers, - // Jobs + /* Server stats */ + ActiveUsers: int(uniqueUserCount), + + /* Jobs */ DataRetentionJobs: dataRetentionJobs, MessageExportJobs: messageExportJobs, ElasticPostIndexingJobs: elasticPostIndexingJobs, @@ -146,6 +186,8 @@ func (a *App) generateSupportPacketYaml() (*model.FileData, error) { MigrationJobs: migrationJobs, } + /* Server stats */ + analytics, appErr := a.GetAnalytics("standard", "") if appErr != nil { rErr = multierror.Append(errors.Wrap(appErr, "error while getting analytics")) diff --git a/server/channels/app/support_packet_test.go b/server/channels/app/support_packet_test.go index f842e8681f..fdcaf170d3 100644 --- a/server/channels/app/support_packet_test.go +++ b/server/channels/app/support_packet_test.go @@ -4,6 +4,7 @@ package app import ( + "errors" "os" "testing" @@ -12,6 +13,8 @@ import ( "gopkg.in/yaml.v2" "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/v8/channels/app/platform" + fmocks "github.com/mattermost/mattermost/server/v8/platform/shared/filestore/mocks" ) func TestCreatePluginsFile(t *testing.T) { @@ -45,16 +48,42 @@ func TestGenerateSupportPacketYaml(t *testing.T) { license.Features.Users = model.NewInt(licenseUsers) th.App.Srv().SetLicense(license) - // Happy path where we have a support packet yaml file without any warnings - fileData, err := th.App.generateSupportPacketYaml() - require.NotNil(t, fileData) - assert.Equal(t, "support_packet.yaml", fileData.Filename) - assert.Positive(t, len(fileData.Body)) - assert.NoError(t, err) - var packet model.SupportPacket - require.NoError(t, yaml.Unmarshal(fileData.Body, &packet)) - assert.Equal(t, 3, packet.ActiveUsers) // from InitBasic. - assert.Equal(t, licenseUsers, packet.LicenseSupportedUsers) + t.Run("Happy path", func(t *testing.T) { + // Happy path where we have a support packet yaml file without any warnings + + fileData, err := th.App.generateSupportPacketYaml() + require.NotNil(t, fileData) + assert.Equal(t, "support_packet.yaml", fileData.Filename) + assert.Positive(t, len(fileData.Body)) + assert.NoError(t, err) + var packet model.SupportPacket + require.NoError(t, yaml.Unmarshal(fileData.Body, &packet)) + + assert.Equal(t, 3, packet.ActiveUsers) // from InitBasic. + assert.Equal(t, licenseUsers, packet.LicenseSupportedUsers) + assert.Empty(t, packet.ClusterID) + assert.Equal(t, "local", packet.FileDriver) + assert.Equal(t, "OK", packet.FileStatus) + }) + + t.Run("filestore fails", func(t *testing.T) { + fb := &fmocks.FileBackend{} + platform.SetFileStore(fb)(th.Server.Platform()) + fb.On("DriverName").Return("mock") + fb.On("TestConnection").Return(errors.New("all broken")) + + fileData, err := th.App.generateSupportPacketYaml() + require.NotNil(t, fileData) + assert.Equal(t, "support_packet.yaml", fileData.Filename) + assert.Positive(t, len(fileData.Body)) + assert.NoError(t, err) + var packet model.SupportPacket + require.NoError(t, yaml.Unmarshal(fileData.Body, &packet)) + + assert.Equal(t, "mock", packet.FileDriver) + assert.Equal(t, "FAIL: all broken", packet.FileStatus) + }) + } func TestGenerateSupportPacket(t *testing.T) { diff --git a/server/platform/shared/filestore/filesstore.go b/server/platform/shared/filestore/filesstore.go index 5e9e5411e4..a26a9554e4 100644 --- a/server/platform/shared/filestore/filesstore.go +++ b/server/platform/shared/filestore/filesstore.go @@ -23,6 +23,7 @@ type ReadCloseSeeker interface { } type FileBackend interface { + DriverName() string TestConnection() error Reader(path string) (ReadCloseSeeker, error) diff --git a/server/platform/shared/filestore/localstore.go b/server/platform/shared/filestore/localstore.go index d8c28d581c..893dac58a2 100644 --- a/server/platform/shared/filestore/localstore.go +++ b/server/platform/shared/filestore/localstore.go @@ -68,6 +68,10 @@ func copyFile(src, dst string) (err error) { return } +func (b *LocalFileBackend) DriverName() string { + return driverLocal +} + func (b *LocalFileBackend) TestConnection() error { f := bytes.NewReader([]byte("testingwrite")) if _, err := writeFileLocally(f, filepath.Join(b.directory, TestFilePath)); err != nil { diff --git a/server/platform/shared/filestore/mocks/FileBackend.go b/server/platform/shared/filestore/mocks/FileBackend.go index 9a37de979f..8d81a03d91 100644 --- a/server/platform/shared/filestore/mocks/FileBackend.go +++ b/server/platform/shared/filestore/mocks/FileBackend.go @@ -57,6 +57,20 @@ func (_m *FileBackend) CopyFile(oldPath string, newPath string) error { return r0 } +// DriverName provides a mock function with given fields: +func (_m *FileBackend) DriverName() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // FileExists provides a mock function with given fields: path func (_m *FileBackend) FileExists(path string) (bool, error) { ret := _m.Called(path) diff --git a/server/platform/shared/filestore/s3store.go b/server/platform/shared/filestore/s3store.go index fed3370152..95008e1c1d 100644 --- a/server/platform/shared/filestore/s3store.go +++ b/server/platform/shared/filestore/s3store.go @@ -184,6 +184,10 @@ func (b *S3FileBackend) s3New(isCloud bool) (*s3.Client, error) { return s3Clnt, nil } +func (b *S3FileBackend) DriverName() string { + return driverS3 +} + func (b *S3FileBackend) TestConnection() error { exists := true var err error diff --git a/server/public/model/system.go b/server/public/model/system.go index 8d573428fc..fb164a8881 100644 --- a/server/public/model/system.go +++ b/server/public/model/system.go @@ -78,31 +78,58 @@ type ServerBusyState struct { } type SupportPacket struct { - LicenseTo string `yaml:"license_to"` - ServerOS string `yaml:"server_os"` - ServerArchitecture string `yaml:"server_architecture"` - ServerVersion string `yaml:"server_version"` - BuildHash string `yaml:"build_hash,omitempty"` - DatabaseType string `yaml:"database_type"` - DatabaseVersion string `yaml:"database_version"` - DatabaseSchemaVersion string `yaml:"database_schema_version"` - LdapVendorName string `yaml:"ldap_vendor_name,omitempty"` - LdapVendorVersion string `yaml:"ldap_vendor_version,omitempty"` - ElasticServerVersion string `yaml:"elastic_server_version,omitempty"` - ElasticServerPlugins []string `yaml:"elastic_server_plugins,omitempty"` - ActiveUsers int `yaml:"active_users"` - LicenseSupportedUsers int `yaml:"license_supported_users,omitempty"` - TotalChannels int `yaml:"total_channels"` - TotalPosts int `yaml:"total_posts"` - TotalTeams int `yaml:"total_teams"` - DailyActiveUsers int `yaml:"daily_active_users"` - MonthlyActiveUsers int `yaml:"monthly_active_users"` - WebsocketConnections int `yaml:"websocket_connections"` - MasterDbConnections int `yaml:"master_db_connections"` - ReplicaDbConnections int `yaml:"read_db_connections"` - InactiveUserCount int `yaml:"inactive_user_count"` + /* Build information */ + + ServerOS string `yaml:"server_os"` + ServerArchitecture string `yaml:"server_architecture"` + ServerVersion string `yaml:"server_version"` + BuildHash string `yaml:"build_hash,omitempty"` + + /* DB */ + + DatabaseType string `yaml:"database_type"` + DatabaseVersion string `yaml:"database_version"` + DatabaseSchemaVersion string `yaml:"database_schema_version"` + WebsocketConnections int `yaml:"websocket_connections"` + MasterDbConnections int `yaml:"master_db_connections"` + ReplicaDbConnections int `yaml:"read_db_connections"` + + /* Cluster */ + + ClusterID string `yaml:"cluster_id"` + + /* File store */ + + FileDriver string `yaml:"file_driver"` + FileStatus string `yaml:"file_status"` + + /* LDAP */ + + LdapVendorName string `yaml:"ldap_vendor_name,omitempty"` + LdapVendorVersion string `yaml:"ldap_vendor_version,omitempty"` + + /* Elastic Search */ + + ElasticServerVersion string `yaml:"elastic_server_version,omitempty"` + ElasticServerPlugins []string `yaml:"elastic_server_plugins,omitempty"` + + /* License */ + + LicenseTo string `yaml:"license_to"` + LicenseSupportedUsers int `yaml:"license_supported_users,omitempty"` + + /* Server stats */ + + ActiveUsers int `yaml:"active_users"` + DailyActiveUsers int `yaml:"daily_active_users"` + MonthlyActiveUsers int `yaml:"monthly_active_users"` + InactiveUserCount int `yaml:"inactive_user_count"` + TotalPosts int `yaml:"total_posts"` + TotalChannels int `yaml:"total_channels"` + TotalTeams int `yaml:"total_teams"` + + /* Jobs */ - // Jobs DataRetentionJobs []*Job `yaml:"data_retention_jobs"` MessageExportJobs []*Job `yaml:"message_export_jobs"` ElasticPostIndexingJobs []*Job `yaml:"elastic_post_indexing_jobs"`