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:""`
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")

View File

@ -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))
}
}
}

View File

@ -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),
)