2022-04-27 03:29:39 -05:00
|
|
|
package store
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2022-10-19 08:02:15 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
2022-04-27 03:29:39 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
|
|
"github.com/grafana/grafana/pkg/registry"
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
|
|
)
|
|
|
|
|
|
|
|
type EntityEventType string
|
|
|
|
|
|
|
|
const (
|
|
|
|
EntityEventTypeDelete EntityEventType = "delete"
|
|
|
|
EntityEventTypeCreate EntityEventType = "create"
|
|
|
|
EntityEventTypeUpdate EntityEventType = "update"
|
|
|
|
)
|
|
|
|
|
|
|
|
type EntityType string
|
|
|
|
|
|
|
|
const (
|
|
|
|
EntityTypeDashboard EntityType = "dashboard"
|
2022-06-03 15:11:32 -05:00
|
|
|
EntityTypeFolder EntityType = "folder"
|
2022-06-15 03:32:29 -05:00
|
|
|
EntityTypeImage EntityType = "image"
|
2022-07-13 12:15:25 -05:00
|
|
|
EntityTypeJSON EntityType = "json"
|
2022-04-27 03:29:39 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// CreateDatabaseEntityId creates entityId for entities stored in the existing SQL tables
|
2023-08-30 10:46:47 -05:00
|
|
|
func CreateDatabaseEntityId(internalId any, orgId int64, entityType EntityType) string {
|
2022-04-27 03:29:39 -05:00
|
|
|
var internalIdAsString string
|
|
|
|
switch id := internalId.(type) {
|
|
|
|
case string:
|
|
|
|
internalIdAsString = id
|
|
|
|
default:
|
|
|
|
internalIdAsString = fmt.Sprintf("%#v", internalId)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("database/%d/%s/%s", orgId, entityType, internalIdAsString)
|
|
|
|
}
|
|
|
|
|
|
|
|
type EntityEvent struct {
|
|
|
|
Id int64
|
|
|
|
EventType EntityEventType
|
|
|
|
EntityId string
|
|
|
|
Created int64
|
|
|
|
}
|
|
|
|
|
|
|
|
type SaveEventCmd struct {
|
|
|
|
EntityId string
|
|
|
|
EventType EntityEventType
|
|
|
|
}
|
|
|
|
|
2022-06-03 15:11:32 -05:00
|
|
|
type EventHandler func(ctx context.Context, e *EntityEvent) error
|
|
|
|
|
2022-04-27 03:29:39 -05:00
|
|
|
// EntityEventsService is a temporary solution to support change notifications in an HA setup
|
|
|
|
// With this service each system can query for any events that have happened since a fixed time
|
2022-09-12 05:03:49 -05:00
|
|
|
//
|
2022-04-27 03:29:39 -05:00
|
|
|
//go:generate mockery --name EntityEventsService --structname MockEntityEventsService --inpackage --filename entity_events_mock.go
|
|
|
|
type EntityEventsService interface {
|
|
|
|
registry.BackgroundService
|
|
|
|
registry.CanBeDisabled
|
|
|
|
GetLastEvent(ctx context.Context) (*EntityEvent, error)
|
|
|
|
GetAllEventsAfter(ctx context.Context, id int64) ([]*EntityEvent, error)
|
|
|
|
|
|
|
|
deleteEventsOlderThan(ctx context.Context, duration time.Duration) error
|
|
|
|
}
|
|
|
|
|
2022-10-19 08:02:15 -05:00
|
|
|
func ProvideEntityEventsService(cfg *setting.Cfg, sqlStore db.DB, features featuremgmt.FeatureToggles) EntityEventsService {
|
2023-11-14 14:50:27 -06:00
|
|
|
if !features.IsEnabledGlobally(featuremgmt.FlagPanelTitleSearch) {
|
2022-04-27 03:29:39 -05:00
|
|
|
return &dummyEntityEventsService{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &entityEventService{
|
2022-06-03 15:11:32 -05:00
|
|
|
sql: sqlStore,
|
|
|
|
features: features,
|
|
|
|
log: log.New("entity-events"),
|
|
|
|
eventHandlers: make([]EventHandler, 0),
|
2022-04-27 03:29:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type entityEventService struct {
|
2022-10-19 08:02:15 -05:00
|
|
|
sql db.DB
|
2022-06-03 15:11:32 -05:00
|
|
|
log log.Logger
|
|
|
|
features featuremgmt.FeatureToggles
|
|
|
|
eventHandlers []EventHandler
|
2022-04-27 03:29:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *entityEventService) GetLastEvent(ctx context.Context) (*EntityEvent, error) {
|
|
|
|
var entityEvent *EntityEvent
|
2022-10-19 08:02:15 -05:00
|
|
|
err := e.sql.WithDbSession(ctx, func(sess *db.Session) error {
|
2022-04-27 03:29:39 -05:00
|
|
|
bean := &EntityEvent{}
|
|
|
|
found, err := sess.OrderBy("id desc").Get(bean)
|
|
|
|
if found {
|
|
|
|
entityEvent = bean
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
|
|
|
return entityEvent, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *entityEventService) GetAllEventsAfter(ctx context.Context, id int64) ([]*EntityEvent, error) {
|
|
|
|
var evs = make([]*EntityEvent, 0)
|
2022-10-19 08:02:15 -05:00
|
|
|
err := e.sql.WithDbSession(ctx, func(sess *db.Session) error {
|
2022-04-27 03:29:39 -05:00
|
|
|
return sess.OrderBy("id asc").Where("id > ?", id).Find(&evs)
|
|
|
|
})
|
|
|
|
|
|
|
|
return evs, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *entityEventService) deleteEventsOlderThan(ctx context.Context, duration time.Duration) error {
|
2022-10-19 08:02:15 -05:00
|
|
|
return e.sql.WithDbSession(ctx, func(sess *db.Session) error {
|
2022-04-27 03:29:39 -05:00
|
|
|
maxCreated := time.Now().Add(-duration)
|
|
|
|
deletedCount, err := sess.Where("created < ?", maxCreated.Unix()).Delete(&EntityEvent{})
|
2023-09-04 11:46:08 -05:00
|
|
|
e.log.Info("Deleting old events", "count", deletedCount, "maxCreated", maxCreated)
|
2022-04-27 03:29:39 -05:00
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *entityEventService) IsDisabled() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *entityEventService) Run(ctx context.Context) error {
|
|
|
|
clean := time.NewTicker(1 * time.Hour)
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-clean.C:
|
|
|
|
go func() {
|
|
|
|
err := e.deleteEventsOlderThan(context.Background(), 24*time.Hour)
|
|
|
|
if err != nil {
|
2023-09-04 11:46:08 -05:00
|
|
|
e.log.Info("Failed to delete old entity events", "error", err)
|
2022-04-27 03:29:39 -05:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
case <-ctx.Done():
|
|
|
|
e.log.Debug("Grafana is shutting down - stopping entity events service")
|
|
|
|
clean.Stop()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type dummyEntityEventsService struct {
|
|
|
|
}
|
|
|
|
|
2023-01-27 06:12:30 -06:00
|
|
|
func NewDummyEntityEventsService() EntityEventsService {
|
|
|
|
return dummyEntityEventsService{}
|
|
|
|
}
|
|
|
|
|
2022-04-27 03:29:39 -05:00
|
|
|
func (d dummyEntityEventsService) Run(ctx context.Context) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d dummyEntityEventsService) IsDisabled() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d dummyEntityEventsService) GetLastEvent(ctx context.Context) (*EntityEvent, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d dummyEntityEventsService) GetAllEventsAfter(ctx context.Context, id int64) ([]*EntityEvent, error) {
|
|
|
|
return make([]*EntityEvent, 0), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d dummyEntityEventsService) deleteEventsOlderThan(ctx context.Context, duration time.Duration) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ EntityEventsService = &dummyEntityEventsService{}
|