2023-02-15 01:52:11 +00:00
|
|
|
package logstore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"math"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/zitadel/logging"
|
|
|
|
|
2023-02-27 21:36:43 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
2023-02-15 01:52:11 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/repository/quota"
|
|
|
|
)
|
|
|
|
|
2023-02-27 21:36:43 +00:00
|
|
|
const handleThresholdTimeout = time.Minute
|
|
|
|
|
2023-02-15 01:52:11 +00:00
|
|
|
type QuotaQuerier interface {
|
|
|
|
GetCurrentQuotaPeriod(ctx context.Context, instanceID string, unit quota.Unit) (config *quota.AddedEvent, periodStart time.Time, err error)
|
2023-03-28 22:09:06 +00:00
|
|
|
GetDueQuotaNotifications(ctx context.Context, config *quota.AddedEvent, periodStart time.Time, used uint64) ([]*quota.NotificationDueEvent, error)
|
2023-02-15 01:52:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type UsageQuerier interface {
|
|
|
|
LogEmitter
|
|
|
|
QuotaUnit() quota.Unit
|
|
|
|
QueryUsage(ctx context.Context, instanceId string, start time.Time) (uint64, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type UsageReporter interface {
|
2023-03-28 22:09:06 +00:00
|
|
|
Report(ctx context.Context, notifications []*quota.NotificationDueEvent) (err error)
|
2023-02-15 01:52:11 +00:00
|
|
|
}
|
|
|
|
|
2023-03-28 22:09:06 +00:00
|
|
|
type UsageReporterFunc func(context.Context, []*quota.NotificationDueEvent) (err error)
|
2023-02-15 01:52:11 +00:00
|
|
|
|
2023-03-28 22:09:06 +00:00
|
|
|
func (u UsageReporterFunc) Report(ctx context.Context, notifications []*quota.NotificationDueEvent) (err error) {
|
2023-02-15 01:52:11 +00:00
|
|
|
return u(ctx, notifications)
|
|
|
|
}
|
|
|
|
|
|
|
|
type Service struct {
|
|
|
|
usageQuerier UsageQuerier
|
|
|
|
quotaQuerier QuotaQuerier
|
|
|
|
usageReporter UsageReporter
|
|
|
|
enabledSinks []*emitter
|
|
|
|
sinkEnabled bool
|
|
|
|
reportingEnabled bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(quotaQuerier QuotaQuerier, usageReporter UsageReporter, usageQuerierSink *emitter, additionalSink ...*emitter) *Service {
|
|
|
|
var usageQuerier UsageQuerier
|
|
|
|
if usageQuerierSink != nil {
|
|
|
|
usageQuerier = usageQuerierSink.emitter.(UsageQuerier)
|
|
|
|
}
|
|
|
|
|
|
|
|
svc := &Service{
|
|
|
|
reportingEnabled: usageQuerierSink != nil && usageQuerierSink.enabled,
|
|
|
|
usageQuerier: usageQuerier,
|
|
|
|
quotaQuerier: quotaQuerier,
|
|
|
|
usageReporter: usageReporter,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, s := range append([]*emitter{usageQuerierSink}, additionalSink...) {
|
|
|
|
if s != nil && s.enabled {
|
|
|
|
svc.enabledSinks = append(svc.enabledSinks, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
svc.sinkEnabled = len(svc.enabledSinks) > 0
|
|
|
|
|
|
|
|
return svc
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) Enabled() bool {
|
|
|
|
return s.sinkEnabled
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) Handle(ctx context.Context, record LogRecord) {
|
|
|
|
for _, sink := range s.enabledSinks {
|
|
|
|
logging.OnError(sink.Emit(ctx, record.Normalize())).WithField("record", record).Warn("failed to emit log record")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) Limit(ctx context.Context, instanceID string) *uint64 {
|
|
|
|
var err error
|
|
|
|
defer func() {
|
|
|
|
logging.OnError(err).Warn("failed to check is usage should be limited")
|
|
|
|
}()
|
|
|
|
|
|
|
|
if !s.reportingEnabled || instanceID == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
quota, periodStart, err := s.quotaQuerier.GetCurrentQuotaPeriod(ctx, instanceID, s.usageQuerier.QuotaUnit())
|
|
|
|
if err != nil || quota == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
usage, err := s.usageQuerier.QueryUsage(ctx, instanceID, periodStart)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-02-27 21:36:43 +00:00
|
|
|
go s.handleThresholds(ctx, quota, periodStart, usage)
|
|
|
|
|
2023-02-15 01:52:11 +00:00
|
|
|
var remaining *uint64
|
|
|
|
if quota.Limit {
|
|
|
|
r := uint64(math.Max(0, float64(quota.Amount)-float64(usage)))
|
|
|
|
remaining = &r
|
|
|
|
}
|
2023-02-27 21:36:43 +00:00
|
|
|
return remaining
|
|
|
|
}
|
2023-02-15 01:52:11 +00:00
|
|
|
|
2023-02-27 21:36:43 +00:00
|
|
|
func (s *Service) handleThresholds(ctx context.Context, quota *quota.AddedEvent, periodStart time.Time, usage uint64) {
|
|
|
|
var err error
|
|
|
|
defer func() {
|
|
|
|
logging.OnError(err).Warn("handling quota thresholds failed")
|
|
|
|
}()
|
|
|
|
|
|
|
|
detatchedCtx, cancel := context.WithTimeout(authz.Detach(ctx), handleThresholdTimeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
notifications, err := s.quotaQuerier.GetDueQuotaNotifications(detatchedCtx, quota, periodStart, usage)
|
|
|
|
if err != nil || len(notifications) == 0 {
|
|
|
|
return
|
2023-02-15 01:52:11 +00:00
|
|
|
}
|
|
|
|
|
2023-02-27 21:36:43 +00:00
|
|
|
err = s.usageReporter.Report(detatchedCtx, notifications)
|
2023-02-15 01:52:11 +00:00
|
|
|
}
|