diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 876155626c5..b6d82125879 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -71,6 +71,7 @@ type HTTPServer struct { PluginManager *plugins.PluginManager `inject:""` SearchService *search.SearchService `inject:""` Live *live.GrafanaLive + Listener net.Listener } func (hs *HTTPServer) Init() error { @@ -119,28 +120,9 @@ func (hs *HTTPServer) Run(ctx context.Context) error { } } - var listener net.Listener - switch setting.Protocol { - case setting.HTTP, setting.HTTPS, setting.HTTP2: - var err error - listener, err = net.Listen("tcp", hs.httpSrv.Addr) - if err != nil { - return errutil.Wrapf(err, "failed to open listener on address %s", hs.httpSrv.Addr) - } - case setting.SOCKET: - var err error - listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: setting.SocketPath, Net: "unix"}) - if err != nil { - return errutil.Wrapf(err, "failed to open listener for socket %s", setting.SocketPath) - } - - // Make socket writable by group - if err := os.Chmod(setting.SocketPath, 0660); err != nil { - return errutil.Wrapf(err, "failed to change socket permissions") - } - default: - hs.log.Error("Invalid protocol", "protocol", setting.Protocol) - return fmt.Errorf("invalid protocol %q", setting.Protocol) + listener, err := hs.getListener() + if err != nil { + return err } hs.log.Info("HTTP Server Listen", "address", listener.Addr().String(), "protocol", @@ -185,6 +167,36 @@ func (hs *HTTPServer) Run(ctx context.Context) error { return nil } +func (hs *HTTPServer) getListener() (net.Listener, error) { + if hs.Listener != nil { + return hs.Listener, nil + } + + switch setting.Protocol { + case setting.HTTP, setting.HTTPS, setting.HTTP2: + listener, err := net.Listen("tcp", hs.httpSrv.Addr) + if err != nil { + return nil, errutil.Wrapf(err, "failed to open listener on address %s", hs.httpSrv.Addr) + } + return listener, nil + case setting.SOCKET: + listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: setting.SocketPath, Net: "unix"}) + if err != nil { + return nil, errutil.Wrapf(err, "failed to open listener for socket %s", setting.SocketPath) + } + + // Make socket writable by group + if err := os.Chmod(setting.SocketPath, 0660); err != nil { + return nil, errutil.Wrapf(err, "failed to change socket permissions") + } + + return listener, nil + default: + hs.log.Error("Invalid protocol", "protocol", setting.Protocol) + return nil, fmt.Errorf("invalid protocol %q", setting.Protocol) + } +} + func (hs *HTTPServer) configureHttps() error { if setting.CertFile == "" { return fmt.Errorf("cert_file cannot be empty when using HTTPS") diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index 56c23db6c0d..d2d1d2bf480 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/extensions" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/server" _ "github.com/grafana/grafana/pkg/services/alerting/conditions" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" @@ -110,14 +111,21 @@ func main() { metrics.SetBuildInformation(version, commit, buildBranch) - server := NewServer(*configFile, *homePath, *pidFile) + s, err := server.New(server.Config{ + ConfigFile: *configFile, HomePath: *homePath, PidFile: *pidFile, + Version: version, Commit: commit, BuildBranch: buildBranch, + }) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } - go listenToSystemSignals(server) + go listenToSystemSignals(s) - err := server.Run() + err = s.Run() code := 0 if err != nil { - code = server.ExitCode(err) + code = s.ExitCode(err) } trace.Stop() log.Close() @@ -135,7 +143,7 @@ func validPackaging(packaging string) string { return "unknown" } -func listenToSystemSignals(server *Server) { +func listenToSystemSignals(s *server.Server) { signalChan := make(chan os.Signal, 1) sighupChan := make(chan os.Signal, 1) @@ -147,7 +155,7 @@ func listenToSystemSignals(server *Server) { case <-sighupChan: log.Reload() case sig := <-signalChan: - server.Shutdown(fmt.Sprintf("System signal: %s", sig)) + s.Shutdown(fmt.Sprintf("System signal: %s", sig)) } } } diff --git a/pkg/cmd/grafana-server/server.go b/pkg/server/server.go similarity index 81% rename from pkg/cmd/grafana-server/server.go rename to pkg/server/server.go index 2297ea663f6..bc6f23203aa 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/server/server.go @@ -1,4 +1,4 @@ -package main +package server import ( "context" @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "strconv" + "sync" "time" "github.com/facebookgo/inject" @@ -43,22 +44,43 @@ import ( "github.com/grafana/grafana/pkg/util/errutil" ) -// NewServer returns a new instance of Server. -func NewServer(configFile, homePath, pidFile string) *Server { +// 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 +} + +// New returns a new instance of Server. +func New(cfg Config) (*Server, error) { rootCtx, shutdownFn := context.WithCancel(context.Background()) childRoutines, childCtx := errgroup.WithContext(rootCtx) - return &Server{ + s := &Server{ context: childCtx, shutdownFn: shutdownFn, childRoutines: childRoutines, log: log.New("server"), cfg: setting.NewCfg(), - configFile: configFile, - homePath: homePath, - pidFile: pidFile, + configFile: cfg.ConfigFile, + homePath: cfg.HomePath, + pidFile: cfg.PidFile, + version: cfg.Version, + commit: cfg.Commit, + buildBranch: cfg.BuildBranch, } + if cfg.Listener != nil { + if err := s.init(&cfg); err != nil { + return nil, err + } + } + + return s, nil } // Server is responsible for managing the lifecycle of services. @@ -70,18 +92,29 @@ type Server struct { cfg *setting.Cfg shutdownReason string shutdownInProgress bool + isInitialized bool + mtx sync.Mutex - configFile string - homePath string - pidFile string + configFile string + homePath string + pidFile string + version string + commit string + buildBranch string - RouteRegister routing.RouteRegister `inject:""` - HTTPServer *api.HTTPServer `inject:""` + HTTPServer *api.HTTPServer `inject:""` } -// 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() (err error) { +// init initializes the server and its services. +func (s *Server) init(cfg *Config) error { + s.mtx.Lock() + defer s.mtx.Unlock() + + if s.isInitialized { + return nil + } + s.isInitialized = true + s.loadConfiguration() s.writePIDFile() @@ -89,9 +122,8 @@ func (s *Server) Run() (err error) { social.NewOAuthService() services := registry.GetServices() - - if err = s.buildServiceGraph(services); err != nil { - return + if err := s.buildServiceGraph(services); err != nil { + return err } // Initialize services. @@ -100,13 +132,33 @@ func (s *Server) Run() (err error) { continue } - s.log.Debug("Initializing " + service.Name) - + if cfg != nil { + 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? + if cfg.Listener != nil { + s.log.Debug("Using provided listener for HTTP server") + httpS.Listener = cfg.Listener + } + } + } if err := service.Instance.Init(); err != nil { return errutil.Wrapf(err, "Service init failed") } } + return nil +} + +// 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() (err error) { + if err = s.init(nil); err != nil { + return + } + + services := registry.GetServices() + // Start background services. for _, svc := range services { service, ok := svc.Instance.(registry.BackgroundService) @@ -157,7 +209,7 @@ func (s *Server) Run() (err error) { s.notifySystemd("READY=1") - return err + return nil } func (s *Server) Shutdown(reason string) { @@ -257,9 +309,9 @@ func (s *Server) loadConfiguration() { } s.log.Info("Starting "+setting.ApplicationName, - "version", version, - "commit", commit, - "branch", buildBranch, + "version", s.version, + "commit", s.commit, + "branch", s.buildBranch, "compiled", time.Unix(setting.BuildStamp, 0), )