Server: Support custom TCP listener (#27066)

* Server: Support custom TCP listener

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Arve Knudsen 2020-08-21 10:41:10 +02:00 committed by GitHub
parent 45adfe7732
commit d53fe64913
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 124 additions and 52 deletions

View File

@ -71,6 +71,7 @@ type HTTPServer struct {
PluginManager *plugins.PluginManager `inject:""` PluginManager *plugins.PluginManager `inject:""`
SearchService *search.SearchService `inject:""` SearchService *search.SearchService `inject:""`
Live *live.GrafanaLive Live *live.GrafanaLive
Listener net.Listener
} }
func (hs *HTTPServer) Init() error { func (hs *HTTPServer) Init() error {
@ -119,28 +120,9 @@ func (hs *HTTPServer) Run(ctx context.Context) error {
} }
} }
var listener net.Listener listener, err := hs.getListener()
switch setting.Protocol { if err != nil {
case setting.HTTP, setting.HTTPS, setting.HTTP2: return err
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)
} }
hs.log.Info("HTTP Server Listen", "address", listener.Addr().String(), "protocol", 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 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 { func (hs *HTTPServer) configureHttps() error {
if setting.CertFile == "" { if setting.CertFile == "" {
return fmt.Errorf("cert_file cannot be empty when using HTTPS") return fmt.Errorf("cert_file cannot be empty when using HTTPS")

View File

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/extensions" "github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics" "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/conditions"
_ "github.com/grafana/grafana/pkg/services/alerting/notifiers" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -110,14 +111,21 @@ func main() {
metrics.SetBuildInformation(version, commit, buildBranch) 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 code := 0
if err != nil { if err != nil {
code = server.ExitCode(err) code = s.ExitCode(err)
} }
trace.Stop() trace.Stop()
log.Close() log.Close()
@ -135,7 +143,7 @@ func validPackaging(packaging string) string {
return "unknown" return "unknown"
} }
func listenToSystemSignals(server *Server) { func listenToSystemSignals(s *server.Server) {
signalChan := make(chan os.Signal, 1) signalChan := make(chan os.Signal, 1)
sighupChan := make(chan os.Signal, 1) sighupChan := make(chan os.Signal, 1)
@ -147,7 +155,7 @@ func listenToSystemSignals(server *Server) {
case <-sighupChan: case <-sighupChan:
log.Reload() log.Reload()
case sig := <-signalChan: case sig := <-signalChan:
server.Shutdown(fmt.Sprintf("System signal: %s", sig)) s.Shutdown(fmt.Sprintf("System signal: %s", sig))
} }
} }
} }

View File

@ -1,4 +1,4 @@
package main package server
import ( import (
"context" "context"
@ -10,6 +10,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/facebookgo/inject" "github.com/facebookgo/inject"
@ -43,22 +44,43 @@ import (
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
) )
// NewServer returns a new instance of Server. // Config contains parameters for the New function.
func NewServer(configFile, homePath, pidFile string) *Server { 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()) rootCtx, shutdownFn := context.WithCancel(context.Background())
childRoutines, childCtx := errgroup.WithContext(rootCtx) childRoutines, childCtx := errgroup.WithContext(rootCtx)
return &Server{ s := &Server{
context: childCtx, context: childCtx,
shutdownFn: shutdownFn, shutdownFn: shutdownFn,
childRoutines: childRoutines, childRoutines: childRoutines,
log: log.New("server"), log: log.New("server"),
cfg: setting.NewCfg(), cfg: setting.NewCfg(),
configFile: configFile, configFile: cfg.ConfigFile,
homePath: homePath, homePath: cfg.HomePath,
pidFile: pidFile, 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. // Server is responsible for managing the lifecycle of services.
@ -70,18 +92,29 @@ type Server struct {
cfg *setting.Cfg cfg *setting.Cfg
shutdownReason string shutdownReason string
shutdownInProgress bool shutdownInProgress bool
isInitialized bool
mtx sync.Mutex
configFile string configFile string
homePath string homePath string
pidFile 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 // init initializes the server and its services.
// exited. To initiate shutdown, call the Shutdown method in another goroutine. func (s *Server) init(cfg *Config) error {
func (s *Server) Run() (err error) { s.mtx.Lock()
defer s.mtx.Unlock()
if s.isInitialized {
return nil
}
s.isInitialized = true
s.loadConfiguration() s.loadConfiguration()
s.writePIDFile() s.writePIDFile()
@ -89,9 +122,8 @@ func (s *Server) Run() (err error) {
social.NewOAuthService() social.NewOAuthService()
services := registry.GetServices() services := registry.GetServices()
if err := s.buildServiceGraph(services); err != nil {
if err = s.buildServiceGraph(services); err != nil { return err
return
} }
// Initialize services. // Initialize services.
@ -100,13 +132,33 @@ func (s *Server) Run() (err error) {
continue 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 { if err := service.Instance.Init(); err != nil {
return errutil.Wrapf(err, "Service init failed") 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. // Start background services.
for _, svc := range services { for _, svc := range services {
service, ok := svc.Instance.(registry.BackgroundService) service, ok := svc.Instance.(registry.BackgroundService)
@ -157,7 +209,7 @@ func (s *Server) Run() (err error) {
s.notifySystemd("READY=1") s.notifySystemd("READY=1")
return err return nil
} }
func (s *Server) Shutdown(reason string) { func (s *Server) Shutdown(reason string) {
@ -257,9 +309,9 @@ func (s *Server) loadConfiguration() {
} }
s.log.Info("Starting "+setting.ApplicationName, s.log.Info("Starting "+setting.ApplicationName,
"version", version, "version", s.version,
"commit", commit, "commit", s.commit,
"branch", buildBranch, "branch", s.buildBranch,
"compiled", time.Unix(setting.BuildStamp, 0), "compiled", time.Unix(setting.BuildStamp, 0),
) )