diff --git a/app/app.go b/app/app.go index c3e66c9923..0051aaca5b 100644 --- a/app/app.go +++ b/app/app.go @@ -142,6 +142,18 @@ func (a *App) getSystemInstallDate() (int64, *model.AppError) { return value, nil } +func (a *App) getFirstServerRunTimestamp() (int64, *model.AppError) { + systemData, appErr := a.Srv().Store.System().GetByName(model.SYSTEM_FIRST_SERVER_RUN_TIMESTAMP_KEY) + if appErr != nil { + return 0, appErr + } + value, err := strconv.ParseInt(systemData.Value, 10, 64) + if err != nil { + return 0, model.NewAppError("getFirstServerRunTimestamp", "app.system_install_date.parse_int.app_error", nil, err.Error(), http.StatusInternalServerError) + } + return value, nil +} + func (a *App) Srv() *Server { return a.srv } diff --git a/app/app_test.go b/app/app_test.go index e19a1bc249..0d2bbb013a 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -39,6 +39,7 @@ func TestUnitUpdateConfig(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore) diff --git a/app/config.go b/app/config.go index 4130ecfefe..183f6d09a1 100644 --- a/app/config.go +++ b/app/config.go @@ -265,6 +265,22 @@ func (a *App) ensureInstallationDate() error { return nil } +func (a *App) ensureFirstServerRunTimestamp() error { + _, err := a.getFirstServerRunTimestamp() + if err == nil { + return nil + } + + err = a.Srv().Store.System().SaveOrUpdate(&model.System{ + Name: model.SYSTEM_FIRST_SERVER_RUN_TIMESTAMP_KEY, + Value: strconv.FormatInt(utils.MillisFromTime(time.Now()), 10), + }) + if err != nil { + return err + } + return nil +} + // AsymmetricSigningKey will return a private key that can be used for asymmetric signing. func (s *Server) AsymmetricSigningKey() *ecdsa.PrivateKey { return s.asymmetricSigningKey diff --git a/app/enterprise_test.go b/app/enterprise_test.go index 9c770d0645..4cd3e041c9 100644 --- a/app/enterprise_test.go +++ b/app/enterprise_test.go @@ -105,6 +105,8 @@ func TestSAMLSettings(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := storemocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore) diff --git a/app/notification_push_test.go b/app/notification_push_test.go index bbd4e29658..727548fb9e 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -555,6 +555,8 @@ func TestGetPushNotificationMessage(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore) @@ -1125,6 +1127,8 @@ func TestClearPushNotificationSync(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockSessionStore := mocks.SessionStore{} mockSessionStore.On("GetSessionsWithActiveDeviceIds", mock.AnythingOfType("string")).Return([]*model.Session{sess1, sess2}, nil) mockSessionStore.On("UpdateDeviceId", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64")).Return("testdeviceID", nil) @@ -1177,6 +1181,8 @@ func TestUpdateMobileAppBadgeSync(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockSessionStore := mocks.SessionStore{} mockSessionStore.On("GetSessionsWithActiveDeviceIds", mock.AnythingOfType("string")).Return([]*model.Session{sess1, sess2}, nil) mockSessionStore.On("UpdateDeviceId", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64")).Return("testdeviceID", nil) @@ -1217,6 +1223,8 @@ func TestSendAckToPushProxy(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore) @@ -1371,6 +1379,8 @@ func BenchmarkPushNotificationThroughput(b *testing.B) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockSessionStore := mocks.SessionStore{} mockPreferenceStore := mocks.PreferenceStore{} mockPreferenceStore.On("Get", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.Preference{Value: "test"}, nil) diff --git a/app/plugin_signature_test.go b/app/plugin_signature_test.go index 9eb5806253..862caecdbe 100644 --- a/app/plugin_signature_test.go +++ b/app/plugin_signature_test.go @@ -27,6 +27,8 @@ func TestPluginPublicKeys(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore) diff --git a/app/post_test.go b/app/post_test.go index aaf8d54285..c675098b6c 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -460,6 +460,8 @@ func TestImageProxy(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := storemocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore) diff --git a/app/server.go b/app/server.go index 83f8465a9b..43e8564763 100644 --- a/app/server.go +++ b/app/server.go @@ -146,7 +146,8 @@ type Server struct { CacheProvider cache.Provider - tracer *tracing.Tracer + tracer *tracing.Tracer + timestampLastDiagnosticSent time.Time } func NewServer(options ...Option) (*Server, error) { @@ -727,11 +728,32 @@ func runSecurityJob(s *Server) { }, time.Hour*4) } -func runDiagnosticsJob(s *Server) { - doDiagnostics(s) - model.CreateRecurringTask("Diagnostics", func() { +func doDiagnosticsIfNeeded(s *Server, firstRun time.Time) { + hoursSinceFirstServerRun := time.Since(firstRun).Hours() + // Send once every 10 minutes for the first hour + // Send once every hour thereafter for the first 12 hours + // Send at the 24 hour mark and every 24 hours after + if hoursSinceFirstServerRun < 1 { doDiagnostics(s) - }, time.Hour*24) + } else if hoursSinceFirstServerRun <= 12 && time.Since(s.timestampLastDiagnosticSent) >= time.Hour { + doDiagnostics(s) + } else if hoursSinceFirstServerRun > 12 && time.Since(s.timestampLastDiagnosticSent) >= 24*time.Hour { + doDiagnostics(s) + } +} + +func runDiagnosticsJob(s *Server) { + // Send on boot + doDiagnostics(s) + firstRun, err := s.FakeApp().getFirstServerRunTimestamp() + if err != nil { + mlog.Warn("Fetching time of first server run failed. Setting to 'now'.") + s.FakeApp().ensureFirstServerRunTimestamp() + firstRun = utils.MillisFromTime(time.Now()) + } + model.CreateRecurringTask("Diagnostics", func() { + doDiagnosticsIfNeeded(s, utils.TimeFromMillis(firstRun)) + }, time.Minute*10) } func runTokenCleanupJob(s *Server) { @@ -761,6 +783,7 @@ func doSecurity(s *Server) { func doDiagnostics(s *Server) { if *s.Config().LogSettings.EnableDiagnostics { + s.timestampLastDiagnosticSent = time.Now() s.FakeApp().SendDailyDiagnostics() } } diff --git a/app/server_app_adapters.go b/app/server_app_adapters.go index 555ec2b584..eb3252757c 100644 --- a/app/server_app_adapters.go +++ b/app/server_app_adapters.go @@ -97,6 +97,10 @@ func (s *Server) RunOldAppInitialization() error { return errors.Wrapf(err, "unable to ensure installation date") } + if err := s.FakeApp().ensureFirstServerRunTimestamp(); err != nil { + return errors.Wrapf(err, "unable to ensure first run timestamp") + } + s.ensureDiagnosticId() s.FakeApp().regenerateClientConfig() diff --git a/model/system.go b/model/system.go index 473292a876..4c3132e2be 100644 --- a/model/system.go +++ b/model/system.go @@ -10,14 +10,15 @@ import ( ) const ( - SYSTEM_DIAGNOSTIC_ID = "DiagnosticId" - SYSTEM_RAN_UNIT_TESTS = "RanUnitTests" - SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime" - SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId" - SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime" - SYSTEM_ASYMMETRIC_SIGNING_KEY = "AsymmetricSigningKey" - SYSTEM_POST_ACTION_COOKIE_SECRET = "PostActionCookieSecret" - SYSTEM_INSTALLATION_DATE_KEY = "InstallationDate" + SYSTEM_DIAGNOSTIC_ID = "DiagnosticId" + SYSTEM_RAN_UNIT_TESTS = "RanUnitTests" + SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime" + SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId" + SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime" + SYSTEM_ASYMMETRIC_SIGNING_KEY = "AsymmetricSigningKey" + SYSTEM_POST_ACTION_COOKIE_SECRET = "PostActionCookieSecret" + SYSTEM_INSTALLATION_DATE_KEY = "InstallationDate" + SYSTEM_FIRST_SERVER_RUN_TIMESTAMP_KEY = "FirstServerRunTimestamp" ) type System struct { diff --git a/testlib/store.go b/testlib/store.go index c5cfeefebe..94a6bfa4fb 100644 --- a/testlib/store.go +++ b/testlib/store.go @@ -27,6 +27,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store { systemStore.On("GetByName", "AsymmetricSigningKey").Return(nil, model.NewAppError("FakeError", "store.sql_system.get_by_name.app_error", nil, "", http.StatusInternalServerError)) systemStore.On("GetByName", "PostActionCookieSecret").Return(nil, model.NewAppError("FakeError", "store.sql_system.get_by_name.app_error", nil, "", http.StatusInternalServerError)) systemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: strconv.FormatInt(model.GetMillis(), 10)}, nil) + systemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) systemStore.On("GetByName", "AdvancedPermissionsMigrationComplete").Return(&model.System{Name: "AdvancedPermissionsMigrationComplete", Value: "true"}, nil) systemStore.On("GetByName", "EmojisPermissionsMigrationComplete").Return(&model.System{Name: "EmojisPermissionsMigrationComplete", Value: "true"}, nil) systemStore.On("GetByName", "GuestRolesCreationMigrationComplete").Return(&model.System{Name: "GuestRolesCreationMigrationComplete", Value: "true"}, nil) diff --git a/web/handlers_test.go b/web/handlers_test.go index 670b5439b1..c4fc13472e 100644 --- a/web/handlers_test.go +++ b/web/handlers_test.go @@ -71,6 +71,8 @@ func TestHandlerServeHTTPSecureTransport(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore) @@ -310,6 +312,8 @@ func TestHandlerServeCSPHeader(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore) @@ -474,6 +478,8 @@ func TestCheckCSRFToken(t *testing.T) { mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) + mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) + mockStore.On("User").Return(&mockUserStore) mockStore.On("Post").Return(&mockPostStore) mockStore.On("System").Return(&mockSystemStore)