feat(GODT-2871): is telemetry enabled as service.

This commit is contained in:
Jakub
2023-08-17 15:08:54 +02:00
parent 5f89e85139
commit f557666b4d
6 changed files with 321 additions and 20 deletions

2
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.17.1-0.20230817061728-c2e6c5429251
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-proton-api v0.4.1-0.20230814133746-f04227131310
github.com/ProtonMail/go-proton-api v0.4.1-0.20230821120427-4da287288372
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton
github.com/PuerkitoBio/goquery v1.8.1
github.com/abiosoft/ishell v2.0.0+incompatible

4
go.sum
View File

@ -35,8 +35,8 @@ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230814133746-f04227131310 h1:phvSTOUN0dIJmt/WSjpQcxcF0jG43+RBtlDpBAYLBKo=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230814133746-f04227131310/go.mod h1:nS8hMGjJLgC0Iej0JMYbsI388LesEkM1Hj/jCCxQeaQ=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230821120427-4da287288372 h1:A5YuTZFJiqcr7axtlUqWEqKOmxyO97KJVZJzl9InwPA=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230821120427-4da287288372/go.mod h1:nS8hMGjJLgC0Iej0JMYbsI388LesEkM1Hj/jCCxQeaQ=
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc=

View File

@ -0,0 +1,161 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package telemetry
import (
"context"
"fmt"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/services/orderedtasks"
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
"github.com/ProtonMail/proton-bridge/v3/pkg/cpc"
"github.com/sirupsen/logrus"
)
type SettingsGetter interface {
GetUserSettings(ctx context.Context) (proton.UserSettings, error)
}
type Service struct {
cpc *cpc.CPC
log *logrus.Entry
eventService userevents.Subscribable
subscription *userevents.EventChanneledSubscriber
userID string
settingsGetter SettingsGetter
isTelemetryEnabled bool
isInitialised bool
}
func NewService(
userID string,
settingsGetter SettingsGetter,
eventService userevents.Subscribable,
) *Service {
s := &Service{
cpc: cpc.NewCPC(),
log: logrus.WithFields(logrus.Fields{
"user": userID,
"service": "telemetry",
}),
eventService: eventService,
subscription: userevents.NewEventSubscriber(
fmt.Sprintf("telemetry-%v", userID),
),
userID: userID,
settingsGetter: settingsGetter,
}
s.initialise()
return s
}
func (s *Service) initialise() {
settings, err := s.settingsGetter.GetUserSettings(context.Background())
if err != nil {
logrus.WithError(err).Error("Cannot get telemetry settings, asuming off")
s.isInitialised = false
s.isTelemetryEnabled = false
return
}
logrus.WithField("telemetry", settings.Telemetry).Debug("Telemetry initialised")
s.isInitialised = true
s.isTelemetryEnabled = settings.Telemetry == proton.SettingEnabled
}
func (s *Service) Start(ctx context.Context, group *orderedtasks.OrderedCancelGroup) {
group.Go(ctx, s.userID, "telemetry-service", s.run)
}
func (s *Service) run(ctx context.Context) {
s.log.Info("Starting service main loop")
defer s.log.Info("Exiting service main loop")
defer s.cpc.Close()
eventHandler := userevents.EventHandler{
RefreshHandler: s,
UserSettingsHandler: s,
}
s.eventService.Subscribe(s.subscription)
defer s.eventService.Unsubscribe(s.subscription)
for {
select {
case <-ctx.Done():
return
case request, ok := <-s.cpc.ReceiveCh():
if !ok {
return
}
switch request.Value().(type) {
case *isTelemetryEnabledReq:
s.log.Debug("Received is telemetry enabled request")
if !s.isInitialised {
s.initialise()
}
request.Reply(ctx, s.isTelemetryEnabled, nil)
default:
s.log.Error("Received unknown request")
}
case e, ok := <-s.subscription.OnEventCh():
if !ok {
continue
}
e.Consume(func(event proton.Event) error {
return eventHandler.OnEvent(ctx, event)
})
}
}
}
func (s *Service) HandleRefreshEvent(_ context.Context, _ proton.RefreshFlag) error {
s.initialise()
return nil
}
func (s *Service) HandleUserSettingsEvent(_ context.Context, settings *proton.UserSettings) error {
s.isTelemetryEnabled = settings.Telemetry == proton.SettingEnabled
s.isInitialised = true
return nil
}
type isTelemetryEnabledReq struct{}
func (s *Service) IsTelemetryEnabled(ctx context.Context) bool {
enabled, err := cpc.SendTyped[bool](ctx, s.cpc, &isTelemetryEnabledReq{})
if err != nil {
s.log.WithError(err).Error("Failed to retrieve IsTelemeteryEnabled, assuming no")
return false
}
return enabled
}

View File

@ -0,0 +1,126 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package telemetry
import (
"context"
"errors"
"testing"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/services/orderedtasks"
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
"github.com/stretchr/testify/require"
)
const TestUserID = "MyUserID"
type mockSettingsGetter struct {
settings proton.UserSettings
err error
}
func (m *mockSettingsGetter) GetUserSettings(_ context.Context) (proton.UserSettings, error) {
return m.settings, m.err
}
func TestService_Unitialised(t *testing.T) {
mockSettingsGetter := &mockSettingsGetter{
settings: proton.UserSettings{Telemetry: proton.SettingEnabled},
err: errors.New("cannot get user settings"),
}
service := NewService(
TestUserID,
mockSettingsGetter,
&userevents.NoOpSubscribable{},
)
require.False(t, service.isInitialised)
require.False(t, service.isTelemetryEnabled)
ctx := context.Background()
group := orderedtasks.NewOrderedCancelGroup(async.NoopPanicHandler{})
defer group.CancelAndWait()
service.Start(ctx, group)
require.False(t, service.IsTelemetryEnabled(ctx))
require.False(t, service.isInitialised)
require.False(t, service.isTelemetryEnabled)
mockSettingsGetter.err = nil
require.True(t, service.IsTelemetryEnabled(ctx))
require.True(t, service.isInitialised)
require.True(t, service.isTelemetryEnabled)
}
func TestService_OnUserSettingsEvent(t *testing.T) {
mockSettingsGetter := &mockSettingsGetter{}
service := NewService(
TestUserID,
mockSettingsGetter,
&userevents.NoOpSubscribable{},
)
require.True(t, service.isInitialised)
ctx := context.Background()
group := orderedtasks.NewOrderedCancelGroup(async.NoopPanicHandler{})
defer group.CancelAndWait()
service.Start(ctx, group)
require.False(t, service.IsTelemetryEnabled(ctx))
require.NoError(t, service.HandleUserSettingsEvent(context.Background(), &proton.UserSettings{Telemetry: proton.SettingEnabled}))
require.True(t, service.IsTelemetryEnabled(ctx))
require.NoError(t, service.HandleUserSettingsEvent(context.Background(), &proton.UserSettings{Telemetry: proton.SettingDisabled}))
require.False(t, service.IsTelemetryEnabled(ctx))
}
func TestService_Unitialised_OnUserSettingsEvent(t *testing.T) {
mockSettingsGetter := &mockSettingsGetter{
settings: proton.UserSettings{Telemetry: proton.SettingEnabled},
err: errors.New("cannot get user settings"),
}
service := NewService(
TestUserID,
mockSettingsGetter,
&userevents.NoOpSubscribable{},
)
require.False(t, service.isInitialised)
require.False(t, service.isTelemetryEnabled)
ctx := context.Background()
group := orderedtasks.NewOrderedCancelGroup(async.NoopPanicHandler{})
defer group.CancelAndWait()
service.Start(ctx, group)
require.False(t, service.IsTelemetryEnabled(ctx))
require.False(t, service.isInitialised)
require.False(t, service.isTelemetryEnabled)
require.NoError(t, service.HandleUserSettingsEvent(context.Background(), &proton.UserSettings{Telemetry: proton.SettingEnabled}))
require.True(t, service.IsTelemetryEnabled(ctx))
require.True(t, service.isInitialised)
require.True(t, service.isTelemetryEnabled)
}

View File

@ -31,12 +31,13 @@ func NewEventSubscriber(name string) *EventChanneledSubscriber {
}
type EventHandler struct {
RefreshHandler RefreshEventHandler
AddressHandler AddressEventHandler
UserHandler UserEventHandler
LabelHandler LabelEventHandler
MessageHandler MessageEventHandler
UsedSpaceHandler UserUsedSpaceEventHandler
RefreshHandler RefreshEventHandler
AddressHandler AddressEventHandler
UserHandler UserEventHandler
LabelHandler LabelEventHandler
MessageHandler MessageEventHandler
UsedSpaceHandler UserUsedSpaceEventHandler
UserSettingsHandler UserSettingsHandler
}
func (e EventHandler) OnEvent(ctx context.Context, event proton.Event) error {
@ -44,7 +45,14 @@ func (e EventHandler) OnEvent(ctx context.Context, event proton.Event) error {
return e.RefreshHandler.HandleRefreshEvent(ctx, event.Refresh)
}
// Start with user events.
// Start with user settings because of telemetry.
if event.UserSettings != nil {
if err := e.UserSettingsHandler.HandleUserSettingsEvent(ctx, event.UserSettings); err != nil {
return fmt.Errorf("failed to apply user event: %w", err)
}
}
// Continue with user events.
if event.User != nil && e.UserHandler != nil {
if err := e.UserHandler.HandleUserEvent(ctx, event.User); err != nil {
return fmt.Errorf("failed to apply user event: %w", err)
@ -93,6 +101,10 @@ type UserUsedSpaceEventHandler interface {
HandleUsedSpaceEvent(ctx context.Context, newSpace int) error
}
type UserSettingsHandler interface {
HandleUserSettingsEvent(ctx context.Context, settings *proton.UserSettings) error
}
type AddressEventHandler interface {
HandleAddressEvents(ctx context.Context, events []proton.AddressEvent) error
}

View File

@ -34,6 +34,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/services/orderedtasks"
"github.com/ProtonMail/proton-bridge/v3/internal/services/sendrecorder"
"github.com/ProtonMail/proton-bridge/v3/internal/services/smtp"
telemetryservice "github.com/ProtonMail/proton-bridge/v3/internal/services/telemetry"
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
"github.com/ProtonMail/proton-bridge/v3/internal/services/useridentity"
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
@ -78,10 +79,11 @@ type User struct {
// goStatusProgress triggers a check/sending if progress is needed.
goStatusProgress func()
eventService *userevents.Service
identityService *useridentity.Service
smtpService *smtp.Service
imapService *imapservice.Service
eventService *userevents.Service
identityService *useridentity.Service
smtpService *smtp.Service
imapService *imapservice.Service
telemetryService *telemetryservice.Service
serviceGroup *orderedtasks.OrderedCancelGroup
}
@ -215,6 +217,8 @@ func newImpl(
user.identityService = useridentity.NewService(user.eventService, user, identityState, encVault, user)
user.telemetryService = telemetryservice.NewService(apiUser.ID, client, user.eventService)
user.smtpService = smtp.NewService(
apiUser.ID,
client,
@ -283,6 +287,9 @@ func newImpl(
return user, fmt.Errorf("failed to start event service: %w", err)
}
// Start Telemetry Service
user.telemetryService.Start(ctx, user.serviceGroup)
// Start Identity Service
user.identityService.Start(ctx, user.serviceGroup)
@ -627,12 +634,7 @@ func (user *User) Close() {
// IsTelemetryEnabled check if the telemetry is enabled or disabled for this user.
func (user *User) IsTelemetryEnabled(ctx context.Context) bool {
settings, err := user.client.GetUserSettings(ctx)
if err != nil {
user.log.WithError(err).Error("Failed to retrieve API user Settings")
return false
}
return settings.Telemetry == proton.SettingEnabled
return user.telemetryService.IsTelemetryEnabled(ctx)
}
// SendTelemetry send telemetry request.