The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
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.
grafana/pkg/server/server.go

354 lines
9.6 KiB

package server
import (
"context"
"errors"
"flag"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strconv"
"sync"
"time"
"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
_ "github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
_ "github.com/grafana/grafana/pkg/infra/remotecache"
_ "github.com/grafana/grafana/pkg/infra/serverlock"
_ "github.com/grafana/grafana/pkg/infra/tracing"
_ "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/login"
_ "github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/middleware"
_ "github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
_ "github.com/grafana/grafana/pkg/services/alerting"
_ "github.com/grafana/grafana/pkg/services/auth"
_ "github.com/grafana/grafana/pkg/services/auth/jwt"
_ "github.com/grafana/grafana/pkg/services/cleanup"
_ "github.com/grafana/grafana/pkg/services/librarypanels"
_ "github.com/grafana/grafana/pkg/services/login/authinfoservice"
_ "github.com/grafana/grafana/pkg/services/login/loginservice"
_ "github.com/grafana/grafana/pkg/services/ngalert"
_ "github.com/grafana/grafana/pkg/services/notifications"
AccessControl: Implement a way to register fixed roles (#35641) * AccessControl: Implement a way to register fixed roles * Add context to register func * Use FixedRoleGrantsMap instead of FixedRoleGrants * Removed FixedRoles map to sync.map * Wrote test for accesscontrol and provisioning * Use mutexes+map instead of sync maps * Create a sync map struct out of a Map and a Mutex * Create a sync map struct for grants as well * Validate builtin roles * Make validation public to access control * Handle errors consistently with what seeder does * Keep errors consistant amongst accesscontrol impl * Handle registration error * Reverse the registration direction thanks to a RoleRegistrant interface * Removed sync map in favor for simple maps since registration now happens during init * Work on the Registrant interface * Remove the Register Role from the interface to have services returning their registrations instead * Adding context to RegisterRegistrantsRoles and update descriptions * little bit of cosmetics * Making sure provisioning is ran after role registration * test for role registration * Change the accesscontrol interface to use a variadic * check if accesscontrol is enabled * Add a new test for RegisterFixedRoles and fix assign which was buggy * Moved RegistrationList def to roles.go * Change provisioning role's description * Better comment on RegisterFixedRoles * Correct comment on ValidateFixedRole * Simplify helper func to removeRoleHelper * Add log to saveFixedRole and assignFixedRole Co-authored-by: Vardan Torosyan <vardants@gmail.com> Co-authored-by: Jeremy Price <Jeremy.price@grafana.com>
4 years ago
"github.com/grafana/grafana/pkg/services/provisioning"
_ "github.com/grafana/grafana/pkg/services/rendering"
_ "github.com/grafana/grafana/pkg/services/search"
_ "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"golang.org/x/sync/errgroup"
)
// Config contains parameters for the New function.
type Config struct {
ConfigFile string
HomePath string
PidFile string
Version string
Commit string
BuildBranch string
Listener net.Listener
}
type serviceRegistry interface {
IsDisabled(srv registry.Service) bool
GetServices() []*registry.Descriptor
}
type globalServiceRegistry struct{}
func (r *globalServiceRegistry) IsDisabled(srv registry.Service) bool {
return registry.IsDisabled(srv)
}
func (r *globalServiceRegistry) GetServices() []*registry.Descriptor {
return registry.GetServices()
}
AccessControl: Implement a way to register fixed roles (#35641) * AccessControl: Implement a way to register fixed roles * Add context to register func * Use FixedRoleGrantsMap instead of FixedRoleGrants * Removed FixedRoles map to sync.map * Wrote test for accesscontrol and provisioning * Use mutexes+map instead of sync maps * Create a sync map struct out of a Map and a Mutex * Create a sync map struct for grants as well * Validate builtin roles * Make validation public to access control * Handle errors consistently with what seeder does * Keep errors consistant amongst accesscontrol impl * Handle registration error * Reverse the registration direction thanks to a RoleRegistrant interface * Removed sync map in favor for simple maps since registration now happens during init * Work on the Registrant interface * Remove the Register Role from the interface to have services returning their registrations instead * Adding context to RegisterRegistrantsRoles and update descriptions * little bit of cosmetics * Making sure provisioning is ran after role registration * test for role registration * Change the accesscontrol interface to use a variadic * check if accesscontrol is enabled * Add a new test for RegisterFixedRoles and fix assign which was buggy * Moved RegistrationList def to roles.go * Change provisioning role's description * Better comment on RegisterFixedRoles * Correct comment on ValidateFixedRole * Simplify helper func to removeRoleHelper * Add log to saveFixedRole and assignFixedRole Co-authored-by: Vardan Torosyan <vardants@gmail.com> Co-authored-by: Jeremy Price <Jeremy.price@grafana.com>
4 years ago
type roleRegistry interface {
// RegisterFixedRoles registers all roles declared to AccessControl
RegisterFixedRoles() error
}
// New returns a new instance of Server.
func New(cfg Config) (*Server, error) {
s := newServer(cfg)
if err := s.init(); err != nil {
return nil, err
}
return s, nil
}
func newServer(cfg Config) *Server {
rootCtx, shutdownFn := context.WithCancel(context.Background())
childRoutines, childCtx := errgroup.WithContext(rootCtx)
return &Server{
context: childCtx,
shutdownFn: shutdownFn,
shutdownFinished: make(chan struct{}),
childRoutines: childRoutines,
log: log.New("server"),
// Need to use the singleton setting.Cfg instance, to make sure we use the same as is injected in the DI
// graph
cfg: setting.GetCfg(),
configFile: cfg.ConfigFile,
homePath: cfg.HomePath,
pidFile: cfg.PidFile,
version: cfg.Version,
commit: cfg.Commit,
buildBranch: cfg.BuildBranch,
serviceRegistry: &globalServiceRegistry{},
listener: cfg.Listener,
}
}
// Server is responsible for managing the lifecycle of services.
type Server struct {
context context.Context
shutdownFn context.CancelFunc
childRoutines *errgroup.Group
log log.Logger
cfg *setting.Cfg
shutdownOnce sync.Once
shutdownFinished chan struct{}
isInitialized bool
mtx sync.Mutex
listener net.Listener
configFile string
homePath string
pidFile string
version string
commit string
buildBranch string
serviceRegistry serviceRegistry
AccessControl: Implement a way to register fixed roles (#35641) * AccessControl: Implement a way to register fixed roles * Add context to register func * Use FixedRoleGrantsMap instead of FixedRoleGrants * Removed FixedRoles map to sync.map * Wrote test for accesscontrol and provisioning * Use mutexes+map instead of sync maps * Create a sync map struct out of a Map and a Mutex * Create a sync map struct for grants as well * Validate builtin roles * Make validation public to access control * Handle errors consistently with what seeder does * Keep errors consistant amongst accesscontrol impl * Handle registration error * Reverse the registration direction thanks to a RoleRegistrant interface * Removed sync map in favor for simple maps since registration now happens during init * Work on the Registrant interface * Remove the Register Role from the interface to have services returning their registrations instead * Adding context to RegisterRegistrantsRoles and update descriptions * little bit of cosmetics * Making sure provisioning is ran after role registration * test for role registration * Change the accesscontrol interface to use a variadic * check if accesscontrol is enabled * Add a new test for RegisterFixedRoles and fix assign which was buggy * Moved RegistrationList def to roles.go * Change provisioning role's description * Better comment on RegisterFixedRoles * Correct comment on ValidateFixedRole * Simplify helper func to removeRoleHelper * Add log to saveFixedRole and assignFixedRole Co-authored-by: Vardan Torosyan <vardants@gmail.com> Co-authored-by: Jeremy Price <Jeremy.price@grafana.com>
4 years ago
HTTPServer *api.HTTPServer `inject:""`
AccessControl roleRegistry `inject:""`
ProvisioningService provisioning.ProvisioningService `inject:""`
}
// init initializes the server and its services.
func (s *Server) init() error {
s.mtx.Lock()
defer s.mtx.Unlock()
if s.isInitialized {
return nil
}
s.isInitialized = true
s.loadConfiguration()
s.writePIDFile()
if err := metrics.SetEnvironmentInformation(s.cfg.MetricsGrafanaEnvironmentInfo); err != nil {
return err
}
login.Init()
services := s.serviceRegistry.GetServices()
if err := s.buildServiceGraph(services); err != nil {
return err
}
if s.listener != nil {
for _, service := range services {
if httpS, ok := service.Instance.(*api.HTTPServer); ok {
// Configure the api.HTTPServer if necessary
// Hopefully we can find a better solution, maybe with a more advanced DI framework, f.ex. Dig?
s.log.Debug("Using provided listener for HTTP server")
httpS.Listener = s.listener
}
}
}
AccessControl: Implement a way to register fixed roles (#35641) * AccessControl: Implement a way to register fixed roles * Add context to register func * Use FixedRoleGrantsMap instead of FixedRoleGrants * Removed FixedRoles map to sync.map * Wrote test for accesscontrol and provisioning * Use mutexes+map instead of sync maps * Create a sync map struct out of a Map and a Mutex * Create a sync map struct for grants as well * Validate builtin roles * Make validation public to access control * Handle errors consistently with what seeder does * Keep errors consistant amongst accesscontrol impl * Handle registration error * Reverse the registration direction thanks to a RoleRegistrant interface * Removed sync map in favor for simple maps since registration now happens during init * Work on the Registrant interface * Remove the Register Role from the interface to have services returning their registrations instead * Adding context to RegisterRegistrantsRoles and update descriptions * little bit of cosmetics * Making sure provisioning is ran after role registration * test for role registration * Change the accesscontrol interface to use a variadic * check if accesscontrol is enabled * Add a new test for RegisterFixedRoles and fix assign which was buggy * Moved RegistrationList def to roles.go * Change provisioning role's description * Better comment on RegisterFixedRoles * Correct comment on ValidateFixedRole * Simplify helper func to removeRoleHelper * Add log to saveFixedRole and assignFixedRole Co-authored-by: Vardan Torosyan <vardants@gmail.com> Co-authored-by: Jeremy Price <Jeremy.price@grafana.com>
4 years ago
// Register all fixed roles
if err := s.AccessControl.RegisterFixedRoles(); err != nil {
return err
}
return s.ProvisioningService.RunInitProvisioners()
}
// Run initializes and starts services. This will block until all services have
// exited. To initiate shutdown, call the Shutdown method in another goroutine.
func (s *Server) Run() error {
defer close(s.shutdownFinished)
if err := s.init(); err != nil {
return err
}
services := s.serviceRegistry.GetServices()
// Start background services.
for _, svc := range services {
service, ok := svc.Instance.(registry.BackgroundService)
if !ok {
continue
}
if s.serviceRegistry.IsDisabled(svc.Instance) {
continue
}
// Variable is needed for accessing loop variable in callback
descriptor := svc
s.childRoutines.Go(func() error {
select {
case <-s.context.Done():
return s.context.Err()
default:
}
err := service.Run(s.context)
// Do not return context.Canceled error since errgroup.Group only
// returns the first error to the caller - thus we can miss a more
// interesting error.
if err != nil && !errors.Is(err, context.Canceled) {
s.log.Error("Stopped "+descriptor.Name, "reason", err)
return fmt.Errorf("%s run error: %w", descriptor.Name, err)
}
s.log.Debug("Stopped "+descriptor.Name, "reason", err)
return nil
})
}
s.notifySystemd("READY=1")
s.log.Debug("Waiting on services...")
return s.childRoutines.Wait()
}
// Shutdown initiates Grafana graceful shutdown. This shuts down all
// running background services. Since Run blocks Shutdown supposed to
// be run from a separate goroutine.
func (s *Server) Shutdown(ctx context.Context, reason string) error {
var err error
s.shutdownOnce.Do(func() {
s.log.Info("Shutdown started", "reason", reason)
// Call cancel func to stop services.
s.shutdownFn()
// Wait for server to shut down
select {
case <-s.shutdownFinished:
s.log.Debug("Finished waiting for server to shut down")
case <-ctx.Done():
s.log.Warn("Timed out while waiting for server to shut down")
err = fmt.Errorf("timeout waiting for shutdown")
}
})
return err
}
// ExitCode returns an exit code for a given error.
func (s *Server) ExitCode(runError error) int {
if runError != nil {
s.log.Error("Server shutdown", "error", runError)
return 1
}
return 0
}
// writePIDFile retrieves the current process ID and writes it to file.
func (s *Server) writePIDFile() {
if s.pidFile == "" {
return
}
// Ensure the required directory structure exists.
err := os.MkdirAll(filepath.Dir(s.pidFile), 0700)
if err != nil {
s.log.Error("Failed to verify pid directory", "error", err)
os.Exit(1)
}
// Retrieve the PID and write it to file.
pid := strconv.Itoa(os.Getpid())
if err := ioutil.WriteFile(s.pidFile, []byte(pid), 0644); err != nil {
s.log.Error("Failed to write pidfile", "error", err)
os.Exit(1)
}
s.log.Info("Writing PID file", "path", s.pidFile, "pid", pid)
}
// buildServiceGraph builds a graph of services and their dependencies.
func (s *Server) buildServiceGraph(services []*registry.Descriptor) error {
// Specify service dependencies.
objs := []interface{}{
bus.GetBus(),
s.cfg,
routing.NewRouteRegister(middleware.ProvideRouteOperationName, middleware.RequestMetrics(s.cfg)),
localcache.New(5*time.Minute, 10*time.Minute),
httpclientprovider.New(s.cfg),
s,
}
return registry.BuildServiceGraph(objs, services)
}
// loadConfiguration loads settings and configuration from config files.
func (s *Server) loadConfiguration() {
args := &setting.CommandLineArgs{
Config: s.configFile,
HomePath: s.homePath,
Args: flag.Args(),
}
if err := s.cfg.Load(args); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed to start grafana. error: %s\n", err.Error())
os.Exit(1)
}
s.log.Info("Starting "+setting.ApplicationName,
"version", s.version,
"commit", s.commit,
"branch", s.buildBranch,
"compiled", time.Unix(setting.BuildStamp, 0),
)
s.cfg.LogConfigSources()
}
// notifySystemd sends state notifications to systemd.
func (s *Server) notifySystemd(state string) {
notifySocket := os.Getenv("NOTIFY_SOCKET")
if notifySocket == "" {
s.log.Debug(
"NOTIFY_SOCKET environment variable empty or unset, can't send systemd notification")
return
}
socketAddr := &net.UnixAddr{
Name: notifySocket,
Net: "unixgram",
}
conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
if err != nil {
s.log.Warn("Failed to connect to systemd", "err", err, "socket", notifySocket)
return
}
defer func() {
if err := conn.Close(); err != nil {
s.log.Warn("Failed to close connection", "err", err)
}
}()
_, err = conn.Write([]byte(state))
if err != nil {
s.log.Warn("Failed to write notification to systemd", "err", err)
}
}