mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
feat(BRIDGE-363): Observability metrics for IMAP connections; minor unleash service refactor;
This commit is contained in:
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ toolchain go1.24.2
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250324123053-2abce471ad71
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250516132429-a4b2de331311
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250417134000-e624a080f7ba
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton
|
||||
|
||||
28
go.sum
28
go.sum
@ -36,8 +36,14 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250324123053-2abce471ad71 h1:UC8SLrS6QbBeOUM8FJugyNoeV5gRGoQCwNePAMxuM20=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250324123053-2abce471ad71/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250513141309-843796a505bc h1:2oppv7H5ZeFnRDohTbLZW5A8I1ylhoX2QEi3RtKxrLE=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250513141309-843796a505bc/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250514104052-2f93fdfc4850 h1:OFMVeakcDS9nHW5kQ/CuBXri84iPBqPgZFHz5Xs/8jo=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250514104052-2f93fdfc4850/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250515084749-4afe6a076ac4 h1:L1JeVS2op3VIcPKctS493+qOBFGhr488mMkYVSLr9eY=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250515084749-4afe6a076ac4/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250516132429-a4b2de331311 h1:8oEkpmF8PD7GyCQjmTto+4yhz4vE1tTT2djL2BgJcBI=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250516132429-a4b2de331311/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||
github.com/ProtonMail/go-crypto v1.1.4-proton h1:KIo9uNlk3vzlwI7o5VjhiEjI4Ld1TDixOMnoNZyfpFE=
|
||||
github.com/ProtonMail/go-crypto v1.1.4-proton/go.mod h1:zNoyBJW3p/yVWiHNZgfTF9VsjwqYof5YY0M9kt2QaX0=
|
||||
@ -45,14 +51,6 @@ github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423 h1:p8nBDx
|
||||
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423/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.20250217140732-2e531f21de4c h1:dxnbB+ov77BDj1LC35fKZ14hLoTpU6OTpZySwxarVx0=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250217140732-2e531f21de4c/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250409092940-13ddc20a05a1 h1:u3G9UB8prOnzOneOf0JFCIVnMRLiK4QgEpPQVu9Y8Q4=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250409092940-13ddc20a05a1/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250409131808-0bbc8e7c32db h1:mOtbY5BB2eNr2QmbZhFn5EnsJcimTntPB6akN2r+AuE=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250409131808-0bbc8e7c32db/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250410050801-92de6e7c8517 h1:70JoDgXxfil4hbDoYGF98rMd47Rld6wXWyFAw4uFOTY=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250410050801-92de6e7c8517/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250417134000-e624a080f7ba h1:DFBngZ7u/f69flRFzPp6Ipo6PKEyflJlA5OCh52yDB4=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250417134000-e624a080f7ba/go.mod h1:eXIoLyIHxvPo8Kd9e1ygYIrAwbeWJhLi3vgSz2crlK4=
|
||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
||||
@ -506,8 +504,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -565,8 +561,6 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -583,8 +577,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -625,8 +617,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@ -648,8 +638,6 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@ -746,7 +746,7 @@ func (bridge *Bridge) PushObservabilityMetric(metric proton.ObservabilityMetric)
|
||||
bridge.observabilityService.AddMetrics(metric)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric) {
|
||||
func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.DistinctionMetricTypeEnum, metrics ...proton.ObservabilityMetric) {
|
||||
bridge.observabilityService.AddDistinctMetrics(errType, metrics...)
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ func NewMockObservabilitySender(ctrl *gomock.Controller) *MockObservabilitySende
|
||||
|
||||
func (m *MockObservabilitySender) EXPECT() *MockObservabilitySenderRecorder { return m.recorder }
|
||||
|
||||
func (m *MockObservabilitySender) AddDistinctMetrics(errType observability.DistinctionErrorTypeEnum, _ ...proton.ObservabilityMetric) {
|
||||
func (m *MockObservabilitySender) AddDistinctMetrics(errType observability.DistinctionMetricTypeEnum, _ ...proton.ObservabilityMetric) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "AddDistinctMetrics", errType)
|
||||
}
|
||||
@ -35,7 +35,18 @@ func (m *MockObservabilitySender) AddMetrics(metrics ...proton.ObservabilityMetr
|
||||
m.ctrl.Call(m, "AddMetrics", metrics)
|
||||
}
|
||||
|
||||
func (mr *MockObservabilitySenderRecorder) AddDistinctMetrics(errType observability.DistinctionErrorTypeEnum, _ ...proton.ObservabilityMetric) *gomock.Call {
|
||||
func (m *MockObservabilitySender) AddTimeLimitedMetric(metricType observability.DistinctionMetricTypeEnum, metric proton.ObservabilityMetric) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "AddTimeLimitedMetric", metricType, metric)
|
||||
}
|
||||
|
||||
func (m *MockObservabilitySender) GetEmailClient() string {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "GetEmailClient")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (mr *MockObservabilitySenderRecorder) AddDistinctMetrics(errType observability.DistinctionMetricTypeEnum, _ ...proton.ObservabilityMetric) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock,
|
||||
"AddDistinctMetrics",
|
||||
@ -47,3 +58,13 @@ func (mr *MockObservabilitySenderRecorder) AddMetrics(metrics ...proton.Observab
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMetrics", reflect.TypeOf((*MockObservabilitySender)(nil).AddMetrics), metrics)
|
||||
}
|
||||
|
||||
func (mr *MockObservabilitySenderRecorder) AddTimeLimitedMetric(metricType observability.DistinctionMetricTypeEnum, metric proton.ObservabilityMetric) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTimeLimitedMetric", reflect.TypeOf((*MockObservabilitySender)(nil).AddTimeLimitedMetric), metricType, metric)
|
||||
}
|
||||
|
||||
func (mr *MockObservabilitySenderRecorder) GetEmailClient() {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
mr.mock.ctrl.Call(mr.mock, "GetEmailClient", reflect.TypeOf((*MockObservabilitySender)(nil).GetEmailClient))
|
||||
}
|
||||
|
||||
@ -551,7 +551,7 @@ func (bridge *Bridge) addUserWithVault(
|
||||
syncSettingsPath,
|
||||
isNew,
|
||||
bridge.notificationStore,
|
||||
bridge.unleashService.GetFlagValue,
|
||||
bridge.unleashService,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create user: %w", err)
|
||||
|
||||
@ -96,7 +96,7 @@ func TestTLSSignedCertTrustedPublicKey(t *testing.T) {
|
||||
|
||||
_, dialer, _, checker, _ := createClientWithPinningDialer("")
|
||||
copyTrustedPins(checker)
|
||||
checker.trustedPins = append(checker.trustedPins, `pin-sha256="hgraU1+uoS6kjiJaH5G+BiqQoyiIml1Nat+2FiUAcII="`)
|
||||
checker.trustedPins = append(checker.trustedPins, `pin-sha256="FlvTPG/nIMKtOj9nelnEjujwSZ5EDyfiKYxZgbXREls="`)
|
||||
_, err := dialer.DialTLSContext(context.Background(), "tcp", "rsa4096.badssl.com:443")
|
||||
r.NoError(t, err, "expected dial to succeed because public key is known and cert is signed by CA")
|
||||
}
|
||||
|
||||
@ -36,6 +36,9 @@ type IMAPServerManager interface {
|
||||
RemoveIMAPUser(ctx context.Context, deleteData bool, provider GluonIDProvider, addrID ...string) error
|
||||
|
||||
LogRemoteLabelIDs(ctx context.Context, provider GluonIDProvider, addrID ...string) error
|
||||
|
||||
GetOpenIMAPSessionCount() int
|
||||
GetRollingIMAPConnectionCount() int
|
||||
}
|
||||
|
||||
type NullIMAPServerManager struct{}
|
||||
@ -67,6 +70,14 @@ func (n NullIMAPServerManager) LogRemoteLabelIDs(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n NullIMAPServerManager) GetOpenIMAPSessionCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (n NullIMAPServerManager) GetRollingIMAPConnectionCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func NewNullIMAPServerManager() *NullIMAPServerManager {
|
||||
return &NullIMAPServerManager{}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gluon"
|
||||
@ -40,6 +41,12 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
rollingCounterNewConnectionThreshold = 300
|
||||
rollingCounterNumberOfBuckets = 6
|
||||
rollingCounterBucketRotationInterval = time.Second * 10
|
||||
)
|
||||
|
||||
var logIMAP = logrus.WithField("pkg", "server/imap") //nolint:gochecknoglobals
|
||||
|
||||
type IMAPSettingsProvider interface {
|
||||
@ -126,6 +133,7 @@ func newIMAPServer(
|
||||
gluon.WithUIDValidityGenerator(uidValidityGenerator),
|
||||
gluon.WithPanicHandler(panicHandler),
|
||||
gluon.WithObservabilitySender(observability.NewAdapter(observabilitySender), int(observability.GluonImapError), int(observability.GluonMessageError), int(observability.GluonOtherError)),
|
||||
gluon.WithConnectionRollingCounter(rollingCounterNewConnectionThreshold, rollingCounterNumberOfBuckets, rollingCounterBucketRotationInterval),
|
||||
}
|
||||
|
||||
if disableIMAPAuthenticate {
|
||||
|
||||
@ -200,6 +200,14 @@ func (sm *Service) RemoveSMTPAccount(ctx context.Context, service *bridgesmtp.Se
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) GetOpenIMAPSessionCount() int {
|
||||
return sm.imapServer.GetOpenSessionCount()
|
||||
}
|
||||
|
||||
func (sm *Service) GetRollingIMAPConnectionCount() int {
|
||||
return sm.imapServer.GetRollingIMAPConnectionCount()
|
||||
}
|
||||
|
||||
func (sm *Service) run(ctx context.Context, subscription events.Subscription) {
|
||||
eventSub := subscription.Add()
|
||||
defer subscription.Remove(eventSub)
|
||||
|
||||
@ -44,7 +44,7 @@ type Service struct {
|
||||
|
||||
store *Store
|
||||
|
||||
getFlagValueFn unleash.GetFlagValueFn
|
||||
featureFlagValueProvider unleash.FeatureFlagValueProvider
|
||||
|
||||
observabilitySender observability.Sender
|
||||
}
|
||||
@ -52,7 +52,7 @@ type Service struct {
|
||||
const bitfieldRegexPattern = `^\\\d+`
|
||||
|
||||
func NewService(userID string, service userevents.Subscribable, eventPublisher events.EventPublisher, store *Store,
|
||||
getFlagFn unleash.GetFlagValueFn, observabilitySender observability.Sender) *Service {
|
||||
featureFlagValueProvider unleash.FeatureFlagValueProvider, observabilitySender observability.Sender) *Service {
|
||||
return &Service{
|
||||
userID: userID,
|
||||
|
||||
@ -68,8 +68,8 @@ func NewService(userID string, service userevents.Subscribable, eventPublisher e
|
||||
|
||||
store: store,
|
||||
|
||||
getFlagValueFn: getFlagFn,
|
||||
observabilitySender: observabilitySender,
|
||||
featureFlagValueProvider: featureFlagValueProvider,
|
||||
observabilitySender: observabilitySender,
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ func (s *Service) run(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEvents []proton.NotificationEvent) error {
|
||||
if s.getFlagValueFn(unleash.EventLoopNotificationDisabled) {
|
||||
if s.featureFlagValueProvider.GetFlagValue(unleash.EventLoopNotificationDisabled) {
|
||||
s.log.Info("Received notification events. Skipping as kill switch is enabled.")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ package observability
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability/gluonmetrics"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
@ -88,6 +89,15 @@ func (adapter *Adapter) AddDistinctMetrics(errType interface{}, metrics ...map[s
|
||||
}
|
||||
|
||||
if len(typedMetrics) > 0 {
|
||||
adapter.sender.AddDistinctMetrics(DistinctionErrorTypeEnum(errTypeInt), typedMetrics...)
|
||||
adapter.sender.AddDistinctMetrics(DistinctionMetricTypeEnum(errTypeInt), typedMetrics...)
|
||||
}
|
||||
}
|
||||
|
||||
func (adapter *Adapter) AddIMAPConnectionsExceededThresholdMetric(totalOpenIMAPConnections, newIMAPConnections int) {
|
||||
metric := gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold(
|
||||
adapter.sender.GetEmailClient(),
|
||||
BucketIMAPConnections(totalOpenIMAPConnections),
|
||||
BucketIMAPConnections(newIMAPConnections))
|
||||
|
||||
adapter.sender.AddTimeLimitedMetric(NewIMAPConnectionsExceedThreshold, metric)
|
||||
}
|
||||
|
||||
@ -19,21 +19,22 @@ package observability
|
||||
|
||||
import "time"
|
||||
|
||||
// DistinctionErrorTypeEnum - maps to the specific error schema for which we
|
||||
// want to send a user update.
|
||||
type DistinctionErrorTypeEnum int
|
||||
// DistinctionMetricTypeEnum - used to distinct specific metrics which we want to limit over some interval.
|
||||
// Most enums are tied to a specific error schema for which we also send a specific distinction user update.
|
||||
type DistinctionMetricTypeEnum int
|
||||
|
||||
const (
|
||||
SyncError DistinctionErrorTypeEnum = iota
|
||||
SyncError DistinctionMetricTypeEnum = iota
|
||||
GluonImapError
|
||||
GluonMessageError
|
||||
GluonOtherError
|
||||
SMTPError
|
||||
EventLoopError // EventLoopError - should always be kept last when inserting new keys.
|
||||
NewIMAPConnectionsExceedThreshold
|
||||
)
|
||||
|
||||
// errorSchemaMap - maps between the DistinctionErrorTypeEnum and the relevant schema name.
|
||||
var errorSchemaMap = map[DistinctionErrorTypeEnum]string{ //nolint:gochecknoglobals
|
||||
// errorSchemaMap - maps between some DistinctionMetricTypeEnum and the relevant schema name.
|
||||
var errorSchemaMap = map[DistinctionMetricTypeEnum]string{ //nolint:gochecknoglobals
|
||||
SyncError: "bridge_sync_errors_users_total",
|
||||
EventLoopError: "bridge_event_loop_events_errors_users_total",
|
||||
GluonImapError: "bridge_gluon_imap_errors_users_total",
|
||||
@ -43,9 +44,9 @@ var errorSchemaMap = map[DistinctionErrorTypeEnum]string{ //nolint:gochecknoglob
|
||||
}
|
||||
|
||||
// createLastSentMap - needs to be updated whenever we make changes to the enum.
|
||||
func createLastSentMap() map[DistinctionErrorTypeEnum]time.Time {
|
||||
func createLastSentMap() map[DistinctionMetricTypeEnum]time.Time {
|
||||
registerTime := time.Now().Add(-updateInterval)
|
||||
lastSentMap := make(map[DistinctionErrorTypeEnum]time.Time)
|
||||
lastSentMap := make(map[DistinctionMetricTypeEnum]time.Time)
|
||||
|
||||
for errType := SyncError; errType <= EventLoopError; errType++ {
|
||||
lastSentMap[errType] = registerTime
|
||||
|
||||
@ -40,7 +40,7 @@ type distinctionUtility struct {
|
||||
|
||||
panicHandler async.PanicHandler
|
||||
|
||||
lastSentMap map[DistinctionErrorTypeEnum]time.Time // Ensures we don't step over the limit of one user update every 5 mins.
|
||||
lastSentMap map[DistinctionMetricTypeEnum]time.Time // Ensures we don't step over the limit of one user update every 5 mins.
|
||||
|
||||
observabilitySender observabilitySender
|
||||
settingsGetter settingsGetter
|
||||
@ -87,7 +87,7 @@ func (d *distinctionUtility) setSettingsGetter(getter settingsGetter) {
|
||||
|
||||
// checkAndUpdateLastSentMap - checks whether we have sent a relevant user update metric
|
||||
// within the last 5 minutes.
|
||||
func (d *distinctionUtility) checkAndUpdateLastSentMap(key DistinctionErrorTypeEnum) bool {
|
||||
func (d *distinctionUtility) checkAndUpdateLastSentMap(key DistinctionMetricTypeEnum) bool {
|
||||
curTime := time.Now()
|
||||
val, ok := d.lastSentMap[key]
|
||||
if !ok {
|
||||
@ -107,7 +107,7 @@ func (d *distinctionUtility) checkAndUpdateLastSentMap(key DistinctionErrorTypeE
|
||||
// and the relevant settings. In the future this will need to be expanded to support multiple
|
||||
// versions of the metric if we ever decide to change them.
|
||||
func (d *distinctionUtility) generateUserMetric(
|
||||
metricType DistinctionErrorTypeEnum,
|
||||
metricType DistinctionMetricTypeEnum,
|
||||
) proton.ObservabilityMetric {
|
||||
schemaName, ok := errorSchemaMap[metricType]
|
||||
if !ok {
|
||||
@ -138,7 +138,7 @@ func generateUserMetric(schemaName, plan, mailClient, dohEnabled, betaAccess str
|
||||
}
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) generateDistinctMetrics(errType DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric) []proton.ObservabilityMetric {
|
||||
func (d *distinctionUtility) generateDistinctMetrics(errType DistinctionMetricTypeEnum, metrics ...proton.ObservabilityMetric) []proton.ObservabilityMetric {
|
||||
d.updateHeartbeatData(errType)
|
||||
|
||||
if d.checkAndUpdateLastSentMap(errType) {
|
||||
|
||||
45
internal/services/observability/gluonmetrics/metrics.go
Normal file
45
internal/services/observability/gluonmetrics/metrics.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2025 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 gluonmetrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
)
|
||||
|
||||
const (
|
||||
newIMAPConnectionThresholdExceededSchemaName = "bridge_imap_recently_opened_connections_total"
|
||||
newIMAPConnectionThresholdExceededVersion = 1
|
||||
)
|
||||
|
||||
func GenerateNewOpenedIMAPConnectionsExceedThreshold(emailClient, totalOpenIMAPConnectionCount, newlyOpenedIMAPConnectionCount string) proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: newIMAPConnectionThresholdExceededSchemaName,
|
||||
Version: newIMAPConnectionThresholdExceededVersion,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{
|
||||
"mailClient": emailClient,
|
||||
"numberOfOpenIMAPConnectionsBuckets": totalOpenIMAPConnectionCount,
|
||||
"numberOfRecentlyOpenedIMAPConnectionsBuckets": newlyOpenedIMAPConnectionCount,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -42,7 +42,7 @@ func (d *distinctionUtility) resetHeartbeatData() {
|
||||
d.heartbeatData.receivedGluonError = false
|
||||
}
|
||||
|
||||
func (d *distinctionUtility) updateHeartbeatData(errType DistinctionErrorTypeEnum) {
|
||||
func (d *distinctionUtility) updateHeartbeatData(errType DistinctionMetricTypeEnum) {
|
||||
d.withUpdateHeartbeatDataLock(func() {
|
||||
//nolint:exhaustive
|
||||
switch errType {
|
||||
|
||||
@ -45,7 +45,9 @@ type client struct {
|
||||
// so we can easily pass them down to relevant components.
|
||||
type Sender interface {
|
||||
AddMetrics(metrics ...proton.ObservabilityMetric)
|
||||
AddDistinctMetrics(errType DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric)
|
||||
AddDistinctMetrics(errType DistinctionMetricTypeEnum, metrics ...proton.ObservabilityMetric)
|
||||
AddTimeLimitedMetric(metricType DistinctionMetricTypeEnum, metric proton.ObservabilityMetric)
|
||||
GetEmailClient() string
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
@ -325,11 +327,25 @@ func (s *Service) AddMetrics(metrics ...proton.ObservabilityMetric) {
|
||||
// what number of events come from what number of users.
|
||||
// As the binning interval is what allows us to do this we
|
||||
// should not send these if there are no logged-in users at that moment.
|
||||
func (s *Service) AddDistinctMetrics(errType DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric) {
|
||||
func (s *Service) AddDistinctMetrics(errType DistinctionMetricTypeEnum, metrics ...proton.ObservabilityMetric) {
|
||||
metrics = s.distinctionUtility.generateDistinctMetrics(errType, metrics...)
|
||||
s.addMetricsIfClients(metrics...)
|
||||
}
|
||||
|
||||
// AddTimeLimitedMetric - schedules a metric to be sent if a metric of the same type has not been sent within some interval.
|
||||
// The interval is defined in the distinction utility.
|
||||
func (s *Service) AddTimeLimitedMetric(metricType DistinctionMetricTypeEnum, metric proton.ObservabilityMetric) {
|
||||
if !s.distinctionUtility.checkAndUpdateLastSentMap(metricType) {
|
||||
return
|
||||
}
|
||||
|
||||
s.addMetricsIfClients(metric)
|
||||
}
|
||||
|
||||
func (s *Service) GetEmailClient() string {
|
||||
return s.distinctionUtility.getEmailClientUserAgent()
|
||||
}
|
||||
|
||||
// ModifyHeartbeatInterval - should only be used for testing. Resets the heartbeat ticker.
|
||||
func (s *Service) ModifyHeartbeatInterval(duration time.Duration) {
|
||||
s.distinctionUtility.heartbeatTicker.Reset(duration)
|
||||
|
||||
@ -66,3 +66,30 @@ func getEnabled(value bool) string {
|
||||
}
|
||||
return "enabled"
|
||||
}
|
||||
|
||||
func BucketIMAPConnections(val int) string {
|
||||
switch {
|
||||
case val < 10:
|
||||
return "<10"
|
||||
case val < 25:
|
||||
return "10-24"
|
||||
case val < 50:
|
||||
return "25-49"
|
||||
case val < 100:
|
||||
return "50-99"
|
||||
case val < 200:
|
||||
return "100-199"
|
||||
case val < 300:
|
||||
return "200-299"
|
||||
case val < 500:
|
||||
return "300-499"
|
||||
case val < 1000:
|
||||
return "500-999"
|
||||
case val < 2000:
|
||||
return "1000-1999"
|
||||
case val < 3000:
|
||||
return "2000-2999"
|
||||
default:
|
||||
return "3000+"
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -29,6 +30,9 @@ const (
|
||||
|
||||
smtpSendSuccessSchemaName = "bridge_smtp_send_success_total"
|
||||
smtpSendSuccessSchemaVersion = 1
|
||||
|
||||
smtpSubmissionRequestSchemaName = "bridge_smtp_send_request_total"
|
||||
smtpSubmissionRequestSchemaVersion = 1
|
||||
)
|
||||
|
||||
func generateSMTPErrorObservabilityMetric(errorType string) proton.ObservabilityMetric {
|
||||
@ -88,3 +92,19 @@ func GenerateSMTPSendSuccess() proton.ObservabilityMetric {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateSMTPSubmissionRequest(emailClient string, numberOfOpenIMAPConnections, numberOfRecentlyOpenedIMAPConnections int) proton.ObservabilityMetric {
|
||||
return proton.ObservabilityMetric{
|
||||
Name: smtpSubmissionRequestSchemaName,
|
||||
Version: smtpSubmissionRequestSchemaVersion,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Data: map[string]interface{}{
|
||||
"Value": 1,
|
||||
"Labels": map[string]string{
|
||||
"numberOfOpenIMAPConnections": observability.BucketIMAPConnections(numberOfOpenIMAPConnections),
|
||||
"numberOfRecentlyOpenedIMAPConnections": observability.BucketIMAPConnections(numberOfRecentlyOpenedIMAPConnections),
|
||||
"mailClient": emailClient,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,13 +32,24 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
"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/observabilitymetrics"
|
||||
"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/unleash"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/usertypes"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/cpc"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
newlyOpenedIMAPConnectionsThreshold = 300
|
||||
)
|
||||
|
||||
type imapSessionCountProvider interface {
|
||||
GetOpenIMAPSessionCount() int
|
||||
GetRollingIMAPConnectionCount() int
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
userID string
|
||||
panicHandler async.PanicHandler
|
||||
@ -59,6 +70,9 @@ type Service struct {
|
||||
serverManager ServerManager
|
||||
|
||||
observabilitySender observability.Sender
|
||||
|
||||
imapSessionCountProvider imapSessionCountProvider
|
||||
featureFlagValueProvider unleash.FeatureFlagValueProvider
|
||||
}
|
||||
|
||||
func NewService(
|
||||
@ -74,6 +88,8 @@ func NewService(
|
||||
identityState *useridentity.State,
|
||||
serverManager ServerManager,
|
||||
observabilitySender observability.Sender,
|
||||
imapSessionCountProvider imapSessionCountProvider,
|
||||
featureFlagValueProvider unleash.FeatureFlagValueProvider,
|
||||
) *Service {
|
||||
subscriberName := fmt.Sprintf("smpt-%v", userID)
|
||||
|
||||
@ -99,7 +115,9 @@ func NewService(
|
||||
addressMode: mode,
|
||||
serverManager: serverManager,
|
||||
|
||||
observabilitySender: observabilitySender,
|
||||
imapSessionCountProvider: imapSessionCountProvider,
|
||||
observabilitySender: observabilitySender,
|
||||
featureFlagValueProvider: featureFlagValueProvider,
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,7 +225,6 @@ func (s *Service) run(ctx context.Context) {
|
||||
|
||||
switch r := request.Value().(type) {
|
||||
case *sendMailReq:
|
||||
s.log.Debug("Received send mail request")
|
||||
err := s.sendMail(ctx, r)
|
||||
request.Reply(ctx, nil, err)
|
||||
|
||||
@ -252,15 +269,38 @@ type sendMailReq struct {
|
||||
|
||||
func (s *Service) sendMail(ctx context.Context, req *sendMailReq) error {
|
||||
defer async.HandlePanic(s.panicHandler)
|
||||
|
||||
openSessionCount := s.imapSessionCountProvider.GetOpenIMAPSessionCount()
|
||||
newlyOpenedSessions := s.imapSessionCountProvider.GetRollingIMAPConnectionCount()
|
||||
log := s.log.WithFields(logrus.Fields{
|
||||
"newlyOpenedIMAPConnectionsCount": newlyOpenedSessions,
|
||||
"openIMAPConnectionsCount": openSessionCount,
|
||||
})
|
||||
log.Debug("Received send mail request")
|
||||
|
||||
// Send SMTP send request metric to observability.
|
||||
s.observabilitySender.AddMetrics(observabilitymetrics.GenerateSMTPSubmissionRequest(s.observabilitySender.GetEmailClient(), openSessionCount, newlyOpenedSessions))
|
||||
|
||||
// Send report to sentry if kill switch is disabled & number of newly opened IMAP connections exceed threshold.
|
||||
if !s.featureFlagValueProvider.GetFlagValue(unleash.SMTPSubmissionRequestSentryReportDisabled) && newlyOpenedSessions >= newlyOpenedIMAPConnectionsThreshold {
|
||||
if err := s.reporter.ReportMessageWithContext("SMTP Send Mail Request - newly opened IMAP connections exceed threshold", reporter.Context{
|
||||
"newlyOpenedIMAPConnectionsCount": newlyOpenedSessions,
|
||||
"openIMAPConnectionsCount": openSessionCount,
|
||||
"emailClient": s.observabilitySender.GetEmailClient(),
|
||||
}); err != nil {
|
||||
s.log.WithError(err).Error("Failed to submit report to sentry (SMTP Send Mail Request)")
|
||||
}
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
end := time.Now()
|
||||
s.log.Debugf("Send mail request finished in %v", end.Sub(start))
|
||||
log.Debugf("Send mail request finished in %v", end.Sub(start))
|
||||
}()
|
||||
|
||||
if err := s.smtpSendMail(ctx, req.authID, req.from, req.to, req.r); err != nil {
|
||||
if apiErr := new(proton.APIError); errors.As(err, &apiErr) {
|
||||
s.log.WithError(apiErr).WithField("Details", apiErr.DetailsToString()).Error("failed to send message")
|
||||
log.WithError(apiErr).WithField("Details", apiErr.DetailsToString()).Error("failed to send message")
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
@ -37,15 +37,29 @@ var pollJitter = 2 * time.Minute //nolint:gochecknoglobals
|
||||
const filename = "unleash_flags"
|
||||
|
||||
const (
|
||||
EventLoopNotificationDisabled = "InboxBridgeEventLoopNotificationDisabled"
|
||||
IMAPAuthenticateCommandDisabled = "InboxBridgeImapAuthenticateCommandDisabled"
|
||||
UserRemovalGluonDataCleanupDisabled = "InboxBridgeUserRemovalGluonDataCleanupDisabled"
|
||||
UpdateUseNewVersionFileStructureDisabled = "InboxBridgeUpdateWithOsFilterDisabled"
|
||||
EventLoopNotificationDisabled = "InboxBridgeEventLoopNotificationDisabled"
|
||||
IMAPAuthenticateCommandDisabled = "InboxBridgeImapAuthenticateCommandDisabled"
|
||||
UserRemovalGluonDataCleanupDisabled = "InboxBridgeUserRemovalGluonDataCleanupDisabled"
|
||||
UpdateUseNewVersionFileStructureDisabled = "InboxBridgeUpdateWithOsFilterDisabled"
|
||||
SMTPSubmissionRequestSentryReportDisabled = "InboxBridgeSmtpSubmissionRequestSentryReportDisabled"
|
||||
)
|
||||
|
||||
type requestFeaturesFn func(ctx context.Context) (proton.FeatureFlagResult, error)
|
||||
type GetFlagValueFn func(key string) bool
|
||||
type FeatureFlagValueProvider interface {
|
||||
GetFlagValue(key string) bool
|
||||
}
|
||||
|
||||
// NullUnleashService - mock of the unleash service. Should be used for testing.
|
||||
type NullUnleashService struct{}
|
||||
|
||||
func (n NullUnleashService) GetFlagValue(_ string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewNullUnleashService() *NullUnleashService {
|
||||
return &NullUnleashService{}
|
||||
}
|
||||
|
||||
type requestFeaturesFn func(ctx context.Context) (proton.FeatureFlagResult, error)
|
||||
type Service struct {
|
||||
panicHandler async.PanicHandler
|
||||
timer *proton.Ticker
|
||||
|
||||
@ -110,7 +110,7 @@ func New(
|
||||
syncConfigDir string,
|
||||
isNew bool,
|
||||
notificationStore *notifications.Store,
|
||||
getFlagValFn unleash.GetFlagValueFn,
|
||||
featureFlagValueProvider unleash.FeatureFlagValueProvider,
|
||||
) (*User, error) {
|
||||
user, err := newImpl(
|
||||
ctx,
|
||||
@ -130,7 +130,7 @@ func New(
|
||||
syncConfigDir,
|
||||
isNew,
|
||||
notificationStore,
|
||||
getFlagValFn,
|
||||
featureFlagValueProvider,
|
||||
)
|
||||
if err != nil {
|
||||
// Cleanup any pending resources on error
|
||||
@ -163,7 +163,7 @@ func newImpl(
|
||||
syncConfigDir string,
|
||||
isNew bool,
|
||||
notificationStore *notifications.Store,
|
||||
getFlagValueFn unleash.GetFlagValueFn,
|
||||
featureFlagValueProvider unleash.FeatureFlagValueProvider,
|
||||
) (*User, error) {
|
||||
logrus.WithField("userID", apiUser.ID).Info("Creating new user")
|
||||
|
||||
@ -262,6 +262,8 @@ func newImpl(
|
||||
identityState.Clone(),
|
||||
smtpServerManager,
|
||||
observabilityService,
|
||||
imapServerManager,
|
||||
featureFlagValueProvider,
|
||||
)
|
||||
|
||||
user.imapService = imapservice.NewService(
|
||||
@ -284,7 +286,7 @@ func newImpl(
|
||||
observabilityService,
|
||||
)
|
||||
|
||||
user.notificationService = notifications.NewService(user.id, user.eventService, user, notificationStore, getFlagValueFn, observabilityService)
|
||||
user.notificationService = notifications.NewService(user.id, user.eventService, user, notificationStore, featureFlagValueProvider, observabilityService)
|
||||
|
||||
// When we receive an auth object, we update it in the vault.
|
||||
// This will be used to authorize the user on the next run.
|
||||
|
||||
@ -33,6 +33,7 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/smtp"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry/mocks"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v3/tests"
|
||||
"github.com/golang/mock/gomock"
|
||||
@ -150,6 +151,7 @@ func withUser(tb testing.TB, ctx context.Context, _ *server.Server, m *proton.Ma
|
||||
nullEventSubscription := events.NewNullSubscription()
|
||||
nullIMAPServerManager := imapservice.NewNullIMAPServerManager()
|
||||
nullSMTPServerManager := smtp.NewNullServerManager()
|
||||
nullUnleashService := unleash.NewNullUnleashService()
|
||||
|
||||
user, err := New(
|
||||
ctx,
|
||||
@ -171,9 +173,7 @@ func withUser(tb testing.TB, ctx context.Context, _ *server.Server, m *proton.Ma
|
||||
notifications.NewStore(func() (string, error) {
|
||||
return "", nil
|
||||
}),
|
||||
func(_ string) bool {
|
||||
return false
|
||||
},
|
||||
nullUnleashService,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
defer user.Close()
|
||||
|
||||
@ -45,5 +45,9 @@ Feature: Bridge send remote notification observability metrics
|
||||
And the user with username "[user:user1]" sends SMTP send success observability metric
|
||||
Then it succeeds
|
||||
|
||||
Scenario: Test SMTP send request observability metric
|
||||
When the user logs in with username "[user:user1]" and password "password"
|
||||
And the user with username "[user:user1]" sends an SMTP send request observability metric
|
||||
Then it succeeds
|
||||
|
||||
|
||||
|
||||
@ -9,3 +9,8 @@ Feature: Bridge send remote notification observability metrics
|
||||
When the user logs in with username "[user:user1]" and password "password"
|
||||
And the user with username "[user:user1]" sends all possible gluon error observability metrics
|
||||
Then it succeeds
|
||||
|
||||
Scenario: Test newly opened IMAP connections in Gluon exceed threshold metric
|
||||
When the user logs in with username "[user:user1]" and password "password"
|
||||
And the user with username "[user:user1]" sends a Gluon metric indicating that the number of newly opened IMAP connections within some interval have exceed a threshold value
|
||||
Then it succeeds
|
||||
|
||||
@ -25,6 +25,7 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice/observabilitymetrics/evtloopmsgevents"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice/observabilitymetrics/syncmsgevents"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability/gluonmetrics"
|
||||
smtpMetrics "github.com/ProtonMail/proton-bridge/v3/internal/services/smtp/observabilitymetrics"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice/observabilitymetrics"
|
||||
)
|
||||
@ -188,3 +189,48 @@ func (s *scenario) SMTPSendSuccessObservabilityMetric(username string) error {
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *scenario) SMTPSendRequestObservabilityMetric(username string) error {
|
||||
batch := proton.ObservabilityBatch{
|
||||
Metrics: []proton.ObservabilityMetric{
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 1, 10),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 10, 25),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 30, 45),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 50, 75),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 100, 150),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 200, 250),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 300, 450),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 500, 750),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 1000, 1500),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 1900, 2500),
|
||||
smtpMetrics.GenerateSMTPSubmissionRequest("outlook", 3000, 3500),
|
||||
},
|
||||
}
|
||||
|
||||
return s.t.withClientPass(context.Background(), username, s.t.getUserByName(username).userPass, func(ctx context.Context, c *proton.Client) error {
|
||||
err := c.SendObservabilityBatch(ctx, batch)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *scenario) GluonNewlyOpenedIMAPConnectionsExceedThreshold(username string) error {
|
||||
batch := proton.ObservabilityBatch{
|
||||
Metrics: []proton.ObservabilityMetric{
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(1), observability.BucketIMAPConnections(10)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(10), observability.BucketIMAPConnections(25)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(30), observability.BucketIMAPConnections(45)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(50), observability.BucketIMAPConnections(75)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(100), observability.BucketIMAPConnections(150)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(200), observability.BucketIMAPConnections(250)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(300), observability.BucketIMAPConnections(450)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(500), observability.BucketIMAPConnections(750)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(1000), observability.BucketIMAPConnections(1500)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(1900), observability.BucketIMAPConnections(2500)),
|
||||
gluonmetrics.GenerateNewOpenedIMAPConnectionsExceedThreshold("outlook", observability.BucketIMAPConnections(3000), observability.BucketIMAPConnections(3500)),
|
||||
},
|
||||
}
|
||||
return s.t.withClientPass(context.Background(), username, s.t.getUserByName(username).userPass, func(ctx context.Context, c *proton.Client) error {
|
||||
err := c.SendObservabilityBatch(ctx, batch)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
@ -242,7 +242,11 @@ func (s *scenario) steps(ctx *godog.ScenarioContext) {
|
||||
// SMTP metrics
|
||||
ctx.Step(`^the user with username "([^"]*)" sends all possible SMTP error observability metrics$`, s.SMTPErrorObservabilityMetrics)
|
||||
ctx.Step(`^the user with username "([^"]*)" sends SMTP send success observability metric$`, s.SMTPSendSuccessObservabilityMetric)
|
||||
// SMTP submission metric
|
||||
ctx.Step(`^the user with username "([^"]*)" sends an SMTP send request observability metric$`, s.SMTPSendRequestObservabilityMetric)
|
||||
|
||||
// Gluon related metrics
|
||||
ctx.Step(`^the user with username "([^"]*)" sends all possible gluon error observability metrics$`, s.testGluonErrorObservabilityMetrics)
|
||||
// Gluon metric - on newly opened IMAP connections exceeding threshold.
|
||||
ctx.Step(`^the user with username "([^"]*)" sends a Gluon metric indicating that the number of newly opened IMAP connections within some interval have exceed a threshold value$`, s.GluonNewlyOpenedIMAPConnectionsExceedThreshold)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user