mirror of https://github.com/grafana/grafana
prometheushacktoberfestmetricsmonitoringalertinggrafanagoinfluxdbmysqlpostgresanalyticsdata-visualizationdashboardbusiness-intelligenceelasticsearch
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
367 lines
9.2 KiB
367 lines
9.2 KiB
|
2 years ago
|
package sqlstash
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"database/sql"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
"sync"
|
||
|
|
|
||
|
|
"github.com/prometheus/client_golang/prometheus"
|
||
|
|
"go.opentelemetry.io/otel/trace"
|
||
|
|
|
||
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
||
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||
|
|
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||
|
|
"github.com/grafana/grafana/pkg/services/sqlstore/session"
|
||
|
|
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
||
|
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash"
|
||
|
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
||
|
2 years ago
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||
|
2 years ago
|
)
|
||
|
|
|
||
|
|
const resoruceTable = "resource"
|
||
|
|
const resourceVersionTable = "resource_version"
|
||
|
|
|
||
|
|
// Package-level errors.
|
||
|
|
var (
|
||
|
|
ErrNotFound = errors.New("entity not found")
|
||
|
|
ErrOptimisticLockingFailed = errors.New("optimistic locking failed")
|
||
|
|
ErrUserNotFoundInContext = errors.New("user not found in context")
|
||
|
|
ErrNextPageTokenNotSupported = errors.New("nextPageToken not yet supported")
|
||
|
|
ErrLimitNotSupported = errors.New("limit not yet supported")
|
||
|
|
ErrNotImplementedYet = errors.New("not implemented yet")
|
||
|
|
)
|
||
|
|
|
||
|
|
// Make sure we implement correct interfaces
|
||
|
2 years ago
|
var _ resource.ResourceStoreServer = &sqlResourceServer{}
|
||
|
2 years ago
|
|
||
|
2 years ago
|
func ProvideSQLResourceServer(db db.EntityDBInterface, tracer tracing.Tracer) (SqlResourceServer, error) {
|
||
|
2 years ago
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
|
|
||
|
|
server := &sqlResourceServer{
|
||
|
|
db: db,
|
||
|
|
log: log.New("sql-resource-server"),
|
||
|
|
ctx: ctx,
|
||
|
|
cancel: cancel,
|
||
|
|
tracer: tracer,
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := prometheus.Register(sqlstash.NewStorageMetrics()); err != nil {
|
||
|
|
server.log.Warn("error registering storage server metrics", "error", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return server, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
type SqlResourceServer interface {
|
||
|
2 years ago
|
resource.ResourceStoreServer
|
||
|
2 years ago
|
|
||
|
|
Init() error
|
||
|
|
Stop()
|
||
|
|
}
|
||
|
|
|
||
|
|
type sqlResourceServer struct {
|
||
|
|
log log.Logger
|
||
|
|
db db.EntityDBInterface // needed to keep xorm engine in scope
|
||
|
|
sess *session.SessionDB
|
||
|
|
dialect migrator.Dialect
|
||
|
2 years ago
|
broadcaster sqlstash.Broadcaster[*resource.WatchResponse]
|
||
|
2 years ago
|
ctx context.Context // TODO: remove
|
||
|
|
cancel context.CancelFunc
|
||
|
2 years ago
|
stream chan *resource.WatchResponse
|
||
|
2 years ago
|
tracer trace.Tracer
|
||
|
2 years ago
|
validator resource.EventValidator
|
||
|
2 years ago
|
|
||
|
|
once sync.Once
|
||
|
|
initErr error
|
||
|
|
|
||
|
|
sqlDB db.DB
|
||
|
|
sqlDialect sqltemplate.Dialect
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *sqlResourceServer) Init() error {
|
||
|
|
s.once.Do(func() {
|
||
|
|
s.initErr = s.init()
|
||
|
|
})
|
||
|
|
|
||
|
|
if s.initErr != nil {
|
||
|
|
return fmt.Errorf("initialize Entity Server: %w", s.initErr)
|
||
|
|
}
|
||
|
|
|
||
|
|
return s.initErr
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *sqlResourceServer) init() error {
|
||
|
|
if s.sess != nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
if s.db == nil {
|
||
|
|
return errors.New("missing db")
|
||
|
|
}
|
||
|
|
|
||
|
|
err := s.db.Init()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
sqlDB, err := s.db.GetDB()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
s.sqlDB = sqlDB
|
||
|
|
|
||
|
|
driverName := sqlDB.DriverName()
|
||
|
|
driverName = strings.TrimSuffix(driverName, "WithHooks")
|
||
|
|
switch driverName {
|
||
|
|
case db.DriverMySQL:
|
||
|
|
s.sqlDialect = sqltemplate.MySQL
|
||
|
|
case db.DriverPostgres:
|
||
|
|
s.sqlDialect = sqltemplate.PostgreSQL
|
||
|
|
case db.DriverSQLite, db.DriverSQLite3:
|
||
|
|
s.sqlDialect = sqltemplate.SQLite
|
||
|
|
default:
|
||
|
|
return fmt.Errorf("no dialect for driver %q", driverName)
|
||
|
|
}
|
||
|
|
|
||
|
|
sess, err := s.db.GetSession()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
engine, err := s.db.GetEngine()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
s.sess = sess
|
||
|
|
s.dialect = migrator.NewDialect(engine.DriverName())
|
||
|
2 years ago
|
s.validator = resource.NewEventValidator(resource.EventValidatorOptions{
|
||
|
2 years ago
|
// use snowflake IDs
|
||
|
|
})
|
||
|
2 years ago
|
|
||
|
|
// set up the broadcaster
|
||
|
2 years ago
|
s.broadcaster, err = sqlstash.NewBroadcaster(s.ctx, func(stream chan *resource.WatchResponse) error {
|
||
|
2 years ago
|
s.stream = stream
|
||
|
|
|
||
|
|
// start the poller
|
||
|
|
go s.poller(stream)
|
||
|
|
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
func (s *sqlResourceServer) IsHealthy(ctx context.Context, r *resource.HealthCheckRequest) (*resource.HealthCheckResponse, error) {
|
||
|
2 years ago
|
ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "isHealthy"}))
|
||
|
|
if err := s.Init(); err != nil {
|
||
|
|
ctxLogger.Error("init error", "error", err)
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := s.sqlDB.PingContext(ctx); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
// TODO: check the status of the watcher implementation as well
|
||
|
2 years ago
|
return &resource.HealthCheckResponse{Status: resource.HealthCheckResponse_SERVING}, nil
|
||
|
2 years ago
|
}
|
||
|
|
|
||
|
|
func (s *sqlResourceServer) Stop() {
|
||
|
|
s.cancel()
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
func (s *sqlResourceServer) GetResource(ctx context.Context, req *resource.GetResourceRequest) (*resource.GetResourceResponse, error) {
|
||
|
2 years ago
|
ctx, span := s.tracer.Start(ctx, "storage_server.GetResource")
|
||
|
|
defer span.End()
|
||
|
|
|
||
|
|
if err := s.Init(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
if req.Key.Group == "" {
|
||
|
2 years ago
|
return &resource.GetResourceResponse{Status: badRequest("missing group")}, nil
|
||
|
2 years ago
|
}
|
||
|
|
if req.Key.Resource == "" {
|
||
|
2 years ago
|
return &resource.GetResourceResponse{Status: badRequest("missing resource")}, nil
|
||
|
2 years ago
|
}
|
||
|
|
|
||
|
2 years ago
|
fmt.Printf("TODO, GET: %+v", req.Key)
|
||
|
|
|
||
|
|
return nil, ErrNotImplementedYet
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
func (s *sqlResourceServer) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResponse, error) {
|
||
|
2 years ago
|
ctx, span := s.tracer.Start(ctx, "storage_server.Create")
|
||
|
|
defer span.End()
|
||
|
|
|
||
|
2 years ago
|
if req.Key.ResourceVersion > 0 {
|
||
|
2 years ago
|
return &resource.CreateResponse{
|
||
|
2 years ago
|
Status: badRequest("can not update a specific resource version"),
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
if err := s.Init(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
event, err := s.validator.PrepareCreate(ctx, req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if event.Status != nil {
|
||
|
2 years ago
|
return &resource.CreateResponse{Status: event.Status}, nil
|
||
|
2 years ago
|
}
|
||
|
|
|
||
|
2 years ago
|
fmt.Printf("TODO, CREATE: %v", event)
|
||
|
2 years ago
|
|
||
|
|
return nil, ErrNotImplementedYet
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
func (s *sqlResourceServer) Update(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResponse, error) {
|
||
|
2 years ago
|
ctx, span := s.tracer.Start(ctx, "storage_server.Update")
|
||
|
|
defer span.End()
|
||
|
|
|
||
|
2 years ago
|
if req.Key.ResourceVersion < 0 {
|
||
|
2 years ago
|
return &resource.UpdateResponse{
|
||
|
2 years ago
|
Status: badRequest("update must include the previous version"),
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
if err := s.Init(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
latest, err := s.GetResource(ctx, &resource.GetResourceRequest{
|
||
|
2 years ago
|
Key: req.Key.WithoutResourceVersion(),
|
||
|
2 years ago
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
2 years ago
|
event, err := s.validator.PrepareUpdate(ctx, req, latest)
|
||
|
2 years ago
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if event.Status != nil {
|
||
|
2 years ago
|
return &resource.UpdateResponse{Status: event.Status}, nil
|
||
|
2 years ago
|
}
|
||
|
|
|
||
|
2 years ago
|
fmt.Printf("TODO, UPDATE: %v", event)
|
||
|
2 years ago
|
|
||
|
|
return nil, ErrNotImplementedYet
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
func (s *sqlResourceServer) Delete(ctx context.Context, req *resource.DeleteRequest) (*resource.DeleteResponse, error) {
|
||
|
2 years ago
|
ctx, span := s.tracer.Start(ctx, "storage_server.Delete")
|
||
|
|
defer span.End()
|
||
|
|
|
||
|
2 years ago
|
if req.Key.ResourceVersion < 0 {
|
||
|
2 years ago
|
return &resource.DeleteResponse{
|
||
|
2 years ago
|
Status: badRequest("update must include the previous version"),
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
if err := s.Init(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
latest, err := s.GetResource(ctx, &resource.GetResourceRequest{
|
||
|
2 years ago
|
Key: req.Key.WithoutResourceVersion(),
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
event, err := s.validator.PrepareDelete(ctx, req, latest)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if event.Status != nil {
|
||
|
2 years ago
|
return &resource.DeleteResponse{Status: event.Status}, nil
|
||
|
2 years ago
|
}
|
||
|
|
|
||
|
|
fmt.Printf("TODO, DELETE: %+v ", req.Key)
|
||
|
2 years ago
|
|
||
|
|
return nil, ErrNotImplementedYet
|
||
|
|
}
|
||
|
|
|
||
|
2 years ago
|
func (s *sqlResourceServer) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResponse, error) {
|
||
|
2 years ago
|
ctx, span := s.tracer.Start(ctx, "storage_server.List")
|
||
|
|
defer span.End()
|
||
|
|
|
||
|
|
if err := s.Init(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
var rv int64
|
||
|
|
err := s.sqlDB.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error {
|
||
|
|
req := sqlResourceVersionGetRequest{
|
||
|
|
SQLTemplate: sqltemplate.New(s.sqlDialect),
|
||
|
|
Group: req.Options.Key.Group,
|
||
|
|
Resource: req.Options.Key.Resource,
|
||
|
|
returnsResourceVersion: new(returnsResourceVersion),
|
||
|
|
}
|
||
|
|
res, err := queryRow(ctx, tx, sqlResourceVersionGet, req)
|
||
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if res != nil {
|
||
|
|
rv = res.ResourceVersion
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf("TODO, LIST: %+v // %d", req.Options.Key, rv)
|
||
|
|
|
||
|
|
return nil, ErrNotImplementedYet
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get the raw blob bytes and metadata
|
||
|
2 years ago
|
func (s *sqlResourceServer) GetBlob(ctx context.Context, req *resource.GetBlobRequest) (*resource.GetBlobResponse, error) {
|
||
|
2 years ago
|
ctx, span := s.tracer.Start(ctx, "storage_server.List")
|
||
|
|
defer span.End()
|
||
|
|
|
||
|
|
if err := s.Init(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf("TODO, GET BLOB: %+v", req.Key)
|
||
|
|
|
||
|
|
return nil, ErrNotImplementedYet
|
||
|
|
}
|
||
|
|
|
||
|
|
// Show resource history (and trash)
|
||
|
2 years ago
|
func (s *sqlResourceServer) History(ctx context.Context, req *resource.HistoryRequest) (*resource.HistoryResponse, error) {
|
||
|
2 years ago
|
ctx, span := s.tracer.Start(ctx, "storage_server.History")
|
||
|
|
defer span.End()
|
||
|
|
|
||
|
|
if err := s.Init(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf("TODO, GET History: %+v", req.Key)
|
||
|
|
|
||
|
|
return nil, ErrNotImplementedYet
|
||
|
|
}
|
||
|
|
|
||
|
|
// Used for efficient provisioning
|
||
|
2 years ago
|
func (s *sqlResourceServer) Origin(ctx context.Context, req *resource.OriginRequest) (*resource.OriginResponse, error) {
|
||
|
2 years ago
|
ctx, span := s.tracer.Start(ctx, "storage_server.History")
|
||
|
|
defer span.End()
|
||
|
|
|
||
|
|
if err := s.Init(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf("TODO, GET History: %+v", req.Key)
|
||
|
|
|
||
|
|
return nil, ErrNotImplementedYet
|
||
|
|
}
|