diff --git a/pkg/modules/dependencies.go b/pkg/modules/dependencies.go new file mode 100644 index 00000000000..6691b4f0872 --- /dev/null +++ b/pkg/modules/dependencies.go @@ -0,0 +1,11 @@ +package modules + +const ( + // All includes all modules necessary for Grafana to run as a standalone application. + All string = "all" +) + +// dependencyMap defines Module Targets => Dependencies +var dependencyMap = map[string][]string{ + All: {}, +} diff --git a/pkg/modules/listener.go b/pkg/modules/listener.go index 79e8bee11c2..3541e811b01 100644 --- a/pkg/modules/listener.go +++ b/pkg/modules/listener.go @@ -21,11 +21,19 @@ func newServiceListener(logger log.Logger, s *service) *serviceListener { } func (l *serviceListener) Healthy() { - l.log.Info("All modules healthy") + l.log.Info("All modules healthy", "modules", l.moduleNames()) } func (l *serviceListener) Stopped() { - l.log.Info("All modules stopped") + l.log.Info("All modules stopped", "modules", l.moduleNames()) +} + +func (l *serviceListener) moduleNames() []string { + var ms []string + for m := range l.service.serviceMap { + ms = append(ms, m) + } + return ms } func (l *serviceListener) Failure(service services.Service) { @@ -35,7 +43,7 @@ func (l *serviceListener) Failure(service services.Service) { } // log which module failed - for module, s := range l.service.ServiceMap { + for module, s := range l.service.serviceMap { if s == service { if errors.Is(service.FailureCase(), modules.ErrStopProcess) { l.log.Info("Received stop signal via return error", "module", module, "err", service.FailureCase()) diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index c970c58a966..6390d300f9c 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -11,11 +11,6 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -// List of available targets. -const ( - All string = "all" -) - type Engine interface { Init(context.Context) error Run(context.Context) error @@ -23,8 +18,8 @@ type Engine interface { } type Manager interface { - RegisterModule(name string, initFn func() (services.Service, error), deps ...string) - RegisterInvisibleModule(name string, initFn func() (services.Service, error), deps ...string) + RegisterModule(name string, initFn func() (services.Service, error)) + RegisterInvisibleModule(name string, initFn func() (services.Service, error)) } var _ Engine = (*service)(nil) @@ -32,27 +27,25 @@ var _ Manager = (*service)(nil) // service manages the registration and lifecycle of modules. type service struct { - cfg *setting.Cfg - log log.Logger - targets []string - dependencyMap map[string][]string + cfg *setting.Cfg + log log.Logger + targets []string - ModuleManager *modules.Manager - ServiceManager *services.Manager - ServiceMap map[string]services.Service + moduleManager *modules.Manager + serviceManager *services.Manager + serviceMap map[string]services.Service } func ProvideService(cfg *setting.Cfg) *service { logger := log.New("modules") return &service{ - cfg: cfg, - log: logger, - targets: cfg.Target, - dependencyMap: map[string][]string{}, + cfg: cfg, + log: logger, + targets: cfg.Target, - ModuleManager: modules.NewManager(logger), - ServiceMap: map[string]services.Service{}, + moduleManager: modules.NewManager(logger), + serviceMap: map[string]services.Service{}, } } @@ -60,30 +53,27 @@ func ProvideService(cfg *setting.Cfg) *service { func (m *service) Init(_ context.Context) error { var err error - // module registration - m.RegisterModule(All, nil) - - for mod, targets := range m.dependencyMap { - if err := m.ModuleManager.AddDependency(mod, targets...); err != nil { + for mod, targets := range dependencyMap { + if err := m.moduleManager.AddDependency(mod, targets...); err != nil { return err } } - m.ServiceMap, err = m.ModuleManager.InitModuleServices(m.targets...) + 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 { + if len(m.serviceMap) == 0 { return nil } var svcs []services.Service - for _, s := range m.ServiceMap { + for _, s := range m.serviceMap { svcs = append(svcs, s) } - m.ServiceManager, err = services.NewManager(svcs...) + m.serviceManager, err = services.NewManager(svcs...) return err } @@ -93,27 +83,27 @@ func (m *service) Run(ctx context.Context) error { // 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 { + if len(m.serviceMap) == 0 { m.log.Warn("No modules registered...") <-ctx.Done() return nil } listener := newServiceListener(m.log, m) - m.ServiceManager.AddListener(listener) + m.serviceManager.AddListener(listener) // wait until a service fails or stop signal was received - err := m.ServiceManager.StartAsync(ctx) + err := m.serviceManager.StartAsync(ctx) if err != nil { return err } - err = m.ServiceManager.AwaitStopped(ctx) + err = m.serviceManager.AwaitStopped(ctx) if err != nil { return err } - failed := m.ServiceManager.ServicesByState()[services.Failed] + 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 @@ -127,26 +117,24 @@ func (m *service) Run(ctx context.Context) error { // Shutdown stops all modules and waits for them to stop. func (m *service) Shutdown(ctx context.Context) error { - if m.ServiceManager == nil { + if m.serviceManager == nil { m.log.Debug("No modules registered, nothing to stop...") return nil } - m.ServiceManager.StopAsync() + m.serviceManager.StopAsync() m.log.Info("Awaiting services to be stopped...") - return m.ServiceManager.AwaitStopped(ctx) + return m.serviceManager.AwaitStopped(ctx) } // RegisterModule registers a module with the dskit module manager. -func (m *service) RegisterModule(name string, initFn func() (services.Service, error), deps ...string) { - m.ModuleManager.RegisterModule(name, initFn) - m.dependencyMap[name] = deps +func (m *service) RegisterModule(name string, initFn func() (services.Service, error)) { + m.moduleManager.RegisterModule(name, initFn) } // RegisterInvisibleModule registers an invisible module with the dskit module manager. -// Invisible modules are not visible to the user, and are intendent to be used as dependencies. -func (m *service) RegisterInvisibleModule(name string, initFn func() (services.Service, error), deps ...string) { - m.ModuleManager.RegisterModule(name, initFn, modules.UserInvisibleModule) - m.dependencyMap[name] = deps +// Invisible modules are not visible to the user, and are intended to be used as dependencies. +func (m *service) RegisterInvisibleModule(name string, initFn func() (services.Service, error)) { + m.moduleManager.RegisterModule(name, initFn, modules.UserInvisibleModule) } // IsModuleEnabled returns true if the module is enabled. diff --git a/pkg/modules/registry/registry.go b/pkg/modules/registry/registry.go new file mode 100644 index 00000000000..b37251cf097 --- /dev/null +++ b/pkg/modules/registry/registry.go @@ -0,0 +1,46 @@ +package registry + +import ( + "github.com/grafana/dskit/services" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/modules" +) + +type Registry interface{} + +type registry struct { + moduleManager modules.Manager + log log.Logger +} + +func ProvideRegistry( + moduleManager modules.Manager, +) *registry { + return newRegistry( + log.New("modules.registry"), + moduleManager, + ) +} + +func newRegistry(logger log.Logger, moduleManager modules.Manager, svcs ...services.NamedService) *registry { + r := ®istry{ + log: logger, + moduleManager: moduleManager, + } + + // Register (invisible) modules which act solely as dependencies to module targets + for _, svc := range svcs { + s := svc + logger.Debug("Registering invisible module", "name", s.ServiceName()) + r.moduleManager.RegisterInvisibleModule(s.ServiceName(), func() (services.Service, error) { + return s, nil + }) + } + + // Register module targets + logger.Debug("Registering module", "name", modules.All) + r.moduleManager.RegisterModule(modules.All, nil) + + return r +} diff --git a/pkg/modules/registry/registry_test.go b/pkg/modules/registry/registry_test.go new file mode 100644 index 00000000000..6f74631141b --- /dev/null +++ b/pkg/modules/registry/registry_test.go @@ -0,0 +1,34 @@ +package registry + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/grafana/dskit/services" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/modules" +) + +func TestRegistry(t *testing.T) { + var registeredInvisibleModules []string + var registeredModules []string + + moduleManager := &modules.MockModuleManager{ + RegisterModuleFunc: func(name string, initFn func() (services.Service, error)) { + registeredModules = append(registeredModules, name) + }, + RegisterInvisibleModuleFunc: func(name string, initFn func() (services.Service, error)) { + registeredInvisibleModules = append(registeredInvisibleModules, name) + }, + } + + mockSvcName := "test-registry" + mockSvc := modules.NewMockNamedService(mockSvcName) + + r := newRegistry(log.New("modules.registry"), moduleManager, mockSvc) + require.NotNil(t, r) + require.Equal(t, []string{mockSvcName}, registeredInvisibleModules) + require.Equal(t, []string{modules.All}, registeredModules) +} diff --git a/pkg/modules/registry/wire.go b/pkg/modules/registry/wire.go new file mode 100644 index 00000000000..2ff95d2acea --- /dev/null +++ b/pkg/modules/registry/wire.go @@ -0,0 +1,8 @@ +package registry + +import "github.com/google/wire" + +var WireSet = wire.NewSet( + ProvideRegistry, + wire.Bind(new(Registry), new(*registry)), +) diff --git a/pkg/modules/util.go b/pkg/modules/util.go index a02479ce9ed..39a2449a5b6 100644 --- a/pkg/modules/util.go +++ b/pkg/modules/util.go @@ -1,5 +1,58 @@ package modules +import ( + "context" + + "github.com/grafana/dskit/services" +) + +var _ Manager = (*MockModuleManager)(nil) +var _ Engine = (*MockModuleEngine)(nil) + +type MockModuleManager struct { + RegisterModuleFunc func(name string, initFn func() (services.Service, error)) + RegisterInvisibleModuleFunc func(name string, initFn func() (services.Service, error)) +} + +func (m *MockModuleManager) RegisterModule(name string, initFn func() (services.Service, error)) { + if m.RegisterModuleFunc != nil { + m.RegisterModuleFunc(name, initFn) + } +} + +func (m *MockModuleManager) RegisterInvisibleModule(name string, initFn func() (services.Service, error)) { + if m.RegisterInvisibleModuleFunc != nil { + m.RegisterInvisibleModuleFunc(name, initFn) + } +} + +type MockModuleEngine struct { + InitFunc func(context.Context) error + RunFunc func(context.Context) error + ShutdownFunc func(context.Context) error +} + +func (m *MockModuleEngine) Init(ctx context.Context) error { + if m.InitFunc != nil { + return m.InitFunc(ctx) + } + return nil +} + +func (m *MockModuleEngine) Run(ctx context.Context) error { + if m.RunFunc != nil { + return m.RunFunc(ctx) + } + return nil +} + +func (m *MockModuleEngine) Shutdown(ctx context.Context) error { + if m.ShutdownFunc != nil { + return m.ShutdownFunc(ctx) + } + return nil +} + func stringsContain(values []string, search string) bool { for _, v := range values { if search == v { @@ -9,3 +62,14 @@ func stringsContain(values []string, search string) bool { return false } + +type MockNamedService struct { + *services.BasicService +} + +func NewMockNamedService(name string) *MockNamedService { + startFn := func(_ context.Context) error { return nil } + return &MockNamedService{ + BasicService: services.NewIdleService(startFn, nil).WithName(name), + } +} diff --git a/pkg/modules/wire.go b/pkg/modules/wire.go index 7bd1c57e124..fe8d0e79744 100644 --- a/pkg/modules/wire.go +++ b/pkg/modules/wire.go @@ -1,6 +1,8 @@ package modules -import "github.com/google/wire" +import ( + "github.com/google/wire" +) var WireSet = wire.NewSet( ProvideService, diff --git a/pkg/server/server.go b/pkg/server/server.go index 2cf67e98cfb..732cd4ce719 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/usagestats/statscollector" "github.com/grafana/grafana/pkg/modules" + moduleRegistry "github.com/grafana/grafana/pkg/modules/registry" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/provisioning" @@ -40,6 +41,7 @@ func New(opts Options, cfg *setting.Cfg, httpServer *api.HTTPServer, roleRegistr provisioningService provisioning.ProvisioningService, backgroundServiceProvider registry.BackgroundServiceRegistry, usageStatsProvidersRegistry registry.UsageStatsProvidersRegistry, statsCollectorService *statscollector.Service, moduleService modules.Engine, + _ moduleRegistry.Registry, // imported to invoke initialization via Wire ) (*Server, error) { statsCollectorService.RegisterProviders(usageStatsProvidersRegistry.GetServices()) s, err := newServer(opts, cfg, httpServer, roleRegistry, provisioningService, backgroundServiceProvider, moduleService) diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 7f74d3eadfa..5ba02cab612 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -31,6 +31,7 @@ import ( "github.com/grafana/grafana/pkg/middleware/csrf" "github.com/grafana/grafana/pkg/middleware/loggermw" "github.com/grafana/grafana/pkg/modules" + moduleRegistry "github.com/grafana/grafana/pkg/modules/registry" pluginDashboards "github.com/grafana/grafana/pkg/plugins/manager/dashboards" "github.com/grafana/grafana/pkg/registry/corekind" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -357,6 +358,7 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(oauthserver.OAuth2Server), new(*oasimpl.OAuth2ServiceImpl)), loggermw.Provide, modules.WireSet, + moduleRegistry.WireSet, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), )