grafana/pkg/modules/modules.go

193 lines
5.2 KiB
Go
Raw Permalink Normal View History

package modules
import (
"context"
"errors"
"github.com/grafana/dskit/modules"
"github.com/grafana/dskit/services"
"github.com/grafana/grafana/pkg/infra/log"
infratracing "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/modules/tracing"
)
type Engine interface {
Run(context.Context) error
Shutdown(context.Context, string) error
}
type Registry interface {
RegisterModule(name string, fn func() (services.Service, error))
RegisterInvisibleModule(name string, fn func() (services.Service, error))
}
type Manager interface {
services.NamedService
Registry
Engine
}
var _ Engine = (*service)(nil)
var _ Registry = (*service)(nil)
// service manages the registration and lifecycle of modules.
type service struct {
services.NamedService
log log.Logger
targets []string
dependencyMap map[string][]string
moduleManager *tracing.ModuleManagerWrapper
serviceManager *services.Manager
serviceMap map[string]services.Service
}
func New(
logger log.Logger,
targets []string,
) *service {
s := &service{
log: logger,
targets: targets,
dependencyMap: dependencyMap,
moduleManager: tracing.WrapModuleManager(modules.NewManager(logger)),
serviceMap: map[string]services.Service{},
}
s.NamedService = services.NewBasicService(s.starting, s.running, s.stopping).WithName("modules.service")
return s
}
func (m *service) WithDependencies(dependencyMap map[string][]string) *service {
m.dependencyMap = dependencyMap
return m
}
func (m *service) starting(ctx context.Context) error {
var err error
m.moduleManager.SetContext(ctx)
_, span := infratracing.Start(ctx, "modules.service.starting")
defer span.End()
for mod, targets := range m.dependencyMap {
if !m.moduleManager.IsModuleRegistered(mod) {
continue
}
if err := m.moduleManager.AddDependency(mod, targets...); err != nil {
return err
}
}
m.serviceMap, err = m.moduleManager.InitModuleServices(m.targets...)
if err != nil {
return err
}
// if no modules are registered, we don't need to start the service manager
if len(m.serviceMap) == 0 {
return nil
}
svcs := make([]services.Service, 0, len(m.serviceMap))
for _, s := range m.serviceMap {
svcs = append(svcs, s)
}
m.serviceManager, err = services.NewManager(svcs...)
if err != nil {
return err
}
// we don't need to continue if no modules are registered.
// this behavior may need to change if dskit services replace the
// current background service registry.
if len(m.serviceMap) == 0 {
m.log.Warn("No modules registered...")
<-ctx.Done()
return nil
}
listener := newServiceListener(m.log, m)
m.serviceManager.AddListener(listener)
if err := m.serviceManager.StartAsync(ctx); err != nil {
return err
}
return m.serviceManager.AwaitHealthy(ctx)
}
func (m *service) running(ctx context.Context) error {
_, span := infratracing.Start(ctx, "modules.service.running")
defer span.End()
// If no service manager was created (no modules registered), just wait for context
if m.serviceManager == nil {
<-ctx.Done()
return nil
}
stopCtx := context.Background()
return m.serviceManager.AwaitStopped(stopCtx)
}
func (m *service) stopping(failureReason error) error {
spanCtx, span := infratracing.Start(context.Background(), "modules.service.stopping")
defer span.End()
m.log.Debug("Stopping module service manager", "reason", failureReason)
// If no service manager was created (no modules registered), nothing to stop
if m.serviceManager == nil {
return nil
}
m.serviceManager.StopAsync()
if err := m.serviceManager.AwaitStopped(spanCtx); err != nil {
m.log.Error("Failed to stop module service manager", "error", err)
return err
}
failed := m.serviceManager.ServicesByState()[services.Failed]
for _, f := range failed {
// the service listener will log error details for all modules that failed,
// so here we return the first error that is not ErrStopProcess
if !errors.Is(f.FailureCase(), modules.ErrStopProcess) {
return f.FailureCase()
}
}
return nil
}
// Run starts all registered modules.
func (m *service) Run(ctx context.Context) error {
spanCtx, span := infratracing.Start(ctx, "modules.service.Run")
defer span.End()
if err := m.StartAsync(spanCtx); err != nil {
return err
}
stopCtx := context.Background()
return m.AwaitTerminated(stopCtx)
}
// Shutdown stops all modules and waits for them to stop.
func (m *service) Shutdown(ctx context.Context, reason string) error {
spanCtx, span := infratracing.Start(ctx, "modules.service.Shutdown")
defer span.End()
m.StopAsync()
return m.AwaitTerminated(spanCtx)
}
// RegisterModule registers a module with the dskit module manager.
func (m *service) RegisterModule(name string, fn func() (services.Service, error)) {
m.moduleManager.RegisterModule(name, fn)
}
// RegisterInvisibleModule registers an invisible module with the dskit module manager.
Storage: Unified Storage based on Entity API (#71977) * first round of entityapi updates - quote column names and clean up insert/update queries - replace grn with guid - streamline table structure fixes streamline entity history move EntitySummary into proto remove EntitySummary add guid to json fix tests change DB_Uuid to DB_NVarchar fix folder test convert interface to any more cleanup start entity store under grafana-apiserver dskit target CRUD working, kind of rough cut of wiring entity api to kube-apiserver fake grafana user in context add key to entity list working revert unnecessary changes move entity storage files to their own package, clean up use accessor to read/write grafana annotations implement separate Create and Update functions * go mod tidy * switch from Kind to resource * basic grpc storage server * basic support for grpc entity store * don't connect to database unless it's needed, pass user identity over grpc * support getting user from k8s context, fix some mysql issues * assign owner to snowflake dependency * switch from ulid to uuid for guids * cleanup, rename Search to List * remove entityListResult * EntityAPI: remove extra user abstraction (#79033) * remove extra user abstraction * add test stub (but * move grpc context setup into client wrapper, fix lint issue * remove unused constants * remove custom json stuff * basic list filtering, add todo * change target to storage-server, allow entityStore flag in prod mode * fix issue with Update * EntityAPI: make test work, need to resolve expected differences (#79123) * make test work, need to resolve expected differences * remove the fields not supported by legacy * sanitize out the bits legacy does not support * sanitize out the bits legacy does not support --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * update feature toggle generated files * remove unused http headers * update feature flag strategy * devmode * update readme * spelling * readme --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2023-12-07 04:21:21 +08:00
// Invisible modules are not visible to the user, and are intended to be used as dependencies.
func (m *service) RegisterInvisibleModule(name string, fn func() (services.Service, error)) {
m.moduleManager.RegisterInvisibleModule(name, fn)
}
func (m *service) IsModuleEnabled(name string) bool {
return stringsContain(m.targets, name)
}