Initial QSfera import
This commit is contained in:
+401
@@ -0,0 +1,401 @@
|
||||
// Copyright 2018-2021 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package grace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Watcher watches a process for a graceful restart
|
||||
// preserving open network sockets to avoid packet loss.
|
||||
type Watcher struct {
|
||||
log zerolog.Logger
|
||||
graceful bool
|
||||
ppid int
|
||||
lns map[string]net.Listener
|
||||
ss map[string]Server
|
||||
pidFile string
|
||||
childPIDs []int
|
||||
gracefulShutdownTimeout int
|
||||
}
|
||||
|
||||
// Option represent an option.
|
||||
type Option func(w *Watcher)
|
||||
|
||||
// WithLogger adds a logger to the Watcher.
|
||||
func WithLogger(l zerolog.Logger) Option {
|
||||
return func(w *Watcher) {
|
||||
w.log = l
|
||||
}
|
||||
}
|
||||
|
||||
// WithPIDFile specifies the pid file to use.
|
||||
func WithPIDFile(fn string) Option {
|
||||
return func(w *Watcher) {
|
||||
w.pidFile = fn
|
||||
}
|
||||
}
|
||||
|
||||
func WithGracefuleShutdownTimeout(seconds int) Option {
|
||||
return func(w *Watcher) {
|
||||
w.gracefulShutdownTimeout = seconds
|
||||
}
|
||||
}
|
||||
|
||||
// NewWatcher creates a Watcher.
|
||||
func NewWatcher(opts ...Option) *Watcher {
|
||||
w := &Watcher{
|
||||
log: zerolog.Nop(),
|
||||
graceful: os.Getenv("GRACEFUL") == "true",
|
||||
ppid: os.Getppid(),
|
||||
ss: map[string]Server{},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(w)
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// Exit exits the current process cleaning up
|
||||
// existing pid files.
|
||||
func (w *Watcher) Exit(errc int) {
|
||||
w.Clean()
|
||||
os.Exit(errc)
|
||||
}
|
||||
|
||||
// Clean removes the pid file.
|
||||
func (w *Watcher) Clean() {
|
||||
err := w.clean()
|
||||
if err != nil {
|
||||
w.log.Warn().Err(err).Msg("error removing pid file")
|
||||
} else {
|
||||
w.log.Info().Msgf("pid file %q got removed", w.pidFile)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) clean() error {
|
||||
// only remove PID file if the PID has been written by us
|
||||
filePID, err := w.readPID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if filePID != os.Getpid() {
|
||||
// the pidfile may have been changed by a forked child
|
||||
// TODO(labkode): is there a way to list children pids for current process?
|
||||
return fmt.Errorf("pid:%d in pidfile is different from pid:%d, it can be a leftover from a hard shutdown or that a reload was triggered", filePID, os.Getpid())
|
||||
}
|
||||
|
||||
return os.Remove(w.pidFile)
|
||||
}
|
||||
|
||||
func (w *Watcher) readPID() (int, error) {
|
||||
piddata, err := os.ReadFile(w.pidFile)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Convert the file contents to an integer.
|
||||
pid, err := strconv.Atoi(string(piddata))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
// GetProcessFromFile reads the pidfile and returns the running process or error if the process or file
|
||||
// are not available.
|
||||
func GetProcessFromFile(pfile string) (*os.Process, error) {
|
||||
data, err := os.ReadFile(pfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pid, err := strconv.Atoi(string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return process, nil
|
||||
}
|
||||
|
||||
// WritePID writes the pid to the configured pid file.
|
||||
func (w *Watcher) WritePID() error {
|
||||
// Read in the pid file as a slice of bytes.
|
||||
if piddata, err := os.ReadFile(w.pidFile); err == nil {
|
||||
// Convert the file contents to an integer.
|
||||
if pid, err := strconv.Atoi(string(piddata)); err == nil {
|
||||
// Look for the pid in the process list.
|
||||
if process, err := os.FindProcess(pid); err == nil {
|
||||
// Send the process a signal zero kill.
|
||||
if err := process.Signal(syscall.Signal(0)); err == nil {
|
||||
if !w.graceful {
|
||||
return fmt.Errorf("pid already running: %d", pid)
|
||||
}
|
||||
|
||||
if pid != w.ppid { // overwrite only if parent pid is pidfile
|
||||
// We only get an error if the pid isn't running, or it's not ours.
|
||||
return fmt.Errorf("pid %d is not this process parent", pid)
|
||||
}
|
||||
} else {
|
||||
w.log.Warn().Err(err).Msg("error sending zero kill signal to current process")
|
||||
}
|
||||
} else {
|
||||
w.log.Warn().Msgf("pid:%d not found", pid)
|
||||
}
|
||||
} else {
|
||||
w.log.Warn().Msg("error casting contents of pidfile to pid(int)")
|
||||
}
|
||||
} // else {
|
||||
// w.log.Info().Msg("error reading pidfile")
|
||||
//}
|
||||
|
||||
// If we get here, then the pidfile didn't exist or we are are in graceful reload and thus we overwrite
|
||||
// or the pid in it doesn't belong to the user running this app.
|
||||
err := os.WriteFile(w.pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0664)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.log.Info().Msgf("pidfile saved at: %s", w.pidFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newListener(network, addr string) (net.Listener, error) {
|
||||
return net.Listen(network, addr)
|
||||
}
|
||||
|
||||
// GetListeners return grpc listener first and http listener second.
|
||||
func (w *Watcher) GetListeners(servers map[string]Server) (map[string]net.Listener, error) {
|
||||
w.ss = servers
|
||||
lns := map[string]net.Listener{}
|
||||
if w.graceful {
|
||||
w.log.Info().Msg("graceful restart, inheriting parent ln fds for grpc and http")
|
||||
count := 3
|
||||
for k, s := range servers {
|
||||
network, addr := s.Network(), s.Address()
|
||||
fd := os.NewFile(uintptr(count), "") // 3 because ExtraFile passed to new process
|
||||
count++
|
||||
ln, err := net.FileListener(fd)
|
||||
if err != nil {
|
||||
w.log.Error().Err(err).Msg("error creating net.Listener from fd")
|
||||
// create new fd
|
||||
ln, err := newListener(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lns[k] = ln
|
||||
} else {
|
||||
lns[k] = ln
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// kill parent
|
||||
// TODO(labkode): maybe race condition here?
|
||||
// What do we do if we cannot kill the parent but we have valid fds?
|
||||
// Do we abort running the forked child? Probably yes, as if the parent cannot be
|
||||
// killed that means we run two version of the code indefinitely.
|
||||
w.log.Info().Msgf("killing parent pid gracefully with SIGQUIT: %d", w.ppid)
|
||||
p, err := os.FindProcess(w.ppid)
|
||||
if err != nil {
|
||||
w.log.Error().Err(err).Msgf("error finding parent process with ppid:%d", w.ppid)
|
||||
err = errors.Wrap(err, "error finding parent process")
|
||||
return nil, err
|
||||
}
|
||||
err = p.Kill()
|
||||
if err != nil {
|
||||
w.log.Error().Err(err).Msgf("error killing parent process with ppid:%d", w.ppid)
|
||||
err = errors.Wrap(err, "error killing parent process")
|
||||
return nil, err
|
||||
}
|
||||
w.lns = lns
|
||||
return lns, nil
|
||||
}
|
||||
|
||||
// create two listeners for grpc and http
|
||||
for k, s := range servers {
|
||||
network, addr := s.Network(), s.Address()
|
||||
ln, err := newListener(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lns[k] = ln
|
||||
|
||||
}
|
||||
w.lns = lns
|
||||
return lns, nil
|
||||
}
|
||||
|
||||
// Server is the interface that servers like HTTP or gRPC
|
||||
// servers need to implement.
|
||||
type Server interface {
|
||||
Stop() error
|
||||
GracefulStop() error
|
||||
Network() string
|
||||
Address() string
|
||||
}
|
||||
|
||||
// TrapSignals captures the OS signal.
|
||||
func (w *Watcher) TrapSignals() {
|
||||
signalCh := make(chan os.Signal, 1024)
|
||||
signal.Notify(signalCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||
for {
|
||||
s := <-signalCh
|
||||
w.log.Info().Msgf("%v signal received", s)
|
||||
|
||||
switch s {
|
||||
case syscall.SIGHUP:
|
||||
w.log.Info().Msg("preparing for a hot-reload, forking child process...")
|
||||
|
||||
// Fork a child process.
|
||||
listeners := w.lns
|
||||
p, err := forkChild(listeners)
|
||||
if err != nil {
|
||||
w.log.Error().Err(err).Msgf("unable to fork child process")
|
||||
} else {
|
||||
w.log.Info().Msgf("child forked with new pid %d", p.Pid)
|
||||
w.childPIDs = append(w.childPIDs, p.Pid)
|
||||
}
|
||||
case syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM:
|
||||
gracefulShutdown(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func gracefulShutdown(w *Watcher) {
|
||||
defer w.Clean()
|
||||
w.log.Info().Int("Timeout", w.gracefulShutdownTimeout).Msg("preparing for a graceful shutdown with deadline")
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for _, s := range w.ss {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
w.log.Info().Str("network.transport", s.Network()).Str("network.local.address", s.Address()).Msg("fd gracefully closed")
|
||||
err := s.GracefulStop()
|
||||
if err != nil {
|
||||
w.log.Error().Err(err).Msg("error stopping server")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(time.Duration(w.gracefulShutdownTimeout) * time.Second):
|
||||
w.log.Info().Msg("graceful shutdown timeout reached. running hard shutdown")
|
||||
for _, s := range w.ss {
|
||||
w.log.Info().Str("network.transport", s.Network()).Str("network.local.address", s.Address()).Msg("fd abruptly closed")
|
||||
err := s.Stop()
|
||||
if err != nil {
|
||||
w.log.Error().Err(err).Msg("error stopping server")
|
||||
}
|
||||
}
|
||||
return
|
||||
case <-done:
|
||||
w.log.Info().Msg("all servers gracefully stopped")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getListenerFile(ln net.Listener) (*os.File, error) {
|
||||
switch t := ln.(type) {
|
||||
case *net.TCPListener:
|
||||
return t.File()
|
||||
case *net.UnixListener:
|
||||
return t.File()
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported listener: %T", ln)
|
||||
}
|
||||
|
||||
func forkChild(lns map[string]net.Listener) (*os.Process, error) {
|
||||
// Get the file descriptor for the listener and marshal the metadata to pass
|
||||
// to the child in the environment.
|
||||
fds := map[string]*os.File{}
|
||||
for k, ln := range lns {
|
||||
fd, err := getListenerFile(ln)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fds[k] = fd
|
||||
}
|
||||
|
||||
// Pass stdin, stdout, and stderr along with the listener file to the child
|
||||
files := []*os.File{
|
||||
os.Stdin,
|
||||
os.Stdout,
|
||||
os.Stderr,
|
||||
}
|
||||
|
||||
// Get current environment and add in the listener to it.
|
||||
environment := append(os.Environ(), "GRACEFUL=true")
|
||||
var counter = 3
|
||||
for k, fd := range fds {
|
||||
k = strings.ToUpper(k)
|
||||
environment = append(environment, k+"FD="+fmt.Sprintf("%d", counter))
|
||||
files = append(files, fd)
|
||||
counter++
|
||||
}
|
||||
|
||||
// Get current process name and directory.
|
||||
execName, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
execDir := filepath.Dir(execName)
|
||||
|
||||
// Spawn child process.
|
||||
p, err := os.StartProcess(execName, os.Args, &os.ProcAttr{
|
||||
Dir: execDir,
|
||||
Env: environment,
|
||||
Files: files,
|
||||
Sys: &syscall.SysProcAttr{},
|
||||
})
|
||||
|
||||
// TODO(labkode): if the process dies (because config changed and is wrong
|
||||
// we need to return an error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/registry"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const (
|
||||
HTTP = iota
|
||||
GRPC
|
||||
)
|
||||
|
||||
// RevaDrivenServer is an interface that defines the methods for starting and stopping reva HTTP/GRPC services.
|
||||
type RevaDrivenServer interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// revaServer is an interface that defines the methods for starting and stopping a reva server.
|
||||
type revaServer interface {
|
||||
Start(ln net.Listener) error
|
||||
Stop() error
|
||||
GracefulStop() error
|
||||
Network() string
|
||||
Address() string
|
||||
}
|
||||
|
||||
// sever represents a generic reva server that implements the RevaDrivenServer interface.
|
||||
type server struct {
|
||||
srv revaServer
|
||||
log *zerolog.Logger
|
||||
gracefulShutdownTimeout time.Duration
|
||||
protocol string
|
||||
}
|
||||
|
||||
// NewDrivenHTTPServerWithOptions runs a revad server w/o watcher with the given config file and options.
|
||||
// Use it in cases where you want to run a revad server without the need for a watcher and the os signal handling as a part of another runtime.
|
||||
// Returns nil if no http server is configured in the config file.
|
||||
// The GracefulShutdownTimeout set to default 20 seconds and can be overridden in the core config.
|
||||
// Logging a fatal error and exit with code 1 if the http server cannot be created.
|
||||
func NewDrivenHTTPServerWithOptions(mainConf map[string]interface{}, opts ...Option) *server {
|
||||
if !isEnabledHTTP(mainConf) {
|
||||
return nil
|
||||
}
|
||||
options := newOptions(opts...)
|
||||
var srv *server
|
||||
var err error
|
||||
if srv, err = newServer(HTTP, mainConf, options); err != nil {
|
||||
options.Logger.Fatal().Err(err).Msg("failed to create http server")
|
||||
}
|
||||
return srv
|
||||
|
||||
}
|
||||
|
||||
// NewDrivenGRPCServerWithOptions runs a revad server w/o watcher with the given config file and options.
|
||||
// Use it in cases where you want to run a revad server without the need for a watcher and the os signal handling as a part of another runtime.
|
||||
// Returns nil if no grpc server is configured in the config file.
|
||||
// The GracefulShutdownTimeout set to default 20 seconds and can be overridden in the core config.
|
||||
// Logging a fatal error and exit with code 1 if the grpc server cannot be created.
|
||||
func NewDrivenGRPCServerWithOptions(mainConf map[string]interface{}, opts ...Option) *server {
|
||||
if !isEnabledGRPC(mainConf) {
|
||||
return nil
|
||||
}
|
||||
options := newOptions(opts...)
|
||||
var srv *server
|
||||
var err error
|
||||
if srv, err = newServer(GRPC, mainConf, options); err != nil {
|
||||
options.Logger.Fatal().Err(err).Msg("failed to create grpc server")
|
||||
}
|
||||
return srv
|
||||
}
|
||||
|
||||
// Start starts the reva server, listening on the configured address and network.
|
||||
func (s *server) Start() error {
|
||||
if s.srv == nil {
|
||||
return fmt.Errorf("reva %s server not initialized", s.protocol)
|
||||
}
|
||||
ln, err := net.Listen(s.srv.Network(), s.srv.Address())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.srv.Start(ln); err != nil {
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
s.log.Error().Err(err).Msg("reva server error")
|
||||
}
|
||||
return err
|
||||
}
|
||||
// update logger with transport and address
|
||||
logger := s.log.With().Str("network.transport", s.srv.Network()).Str("network.local.address", s.srv.Address()).Logger()
|
||||
s.log = &logger
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop gracefully stops the reva server, waiting for the graceful shutdown timeout.
|
||||
func (s *server) Stop() error {
|
||||
if s.srv == nil {
|
||||
return nil
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
s.log.Info().Msg("gracefully stopping reva server")
|
||||
if err := s.srv.GracefulStop(); err != nil {
|
||||
s.log.Error().Err(err).Msg("error gracefully stopping reva server")
|
||||
err := s.srv.Stop()
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msg("error stopping reva server")
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(s.gracefulShutdownTimeout):
|
||||
s.log.Info().Msg("graceful shutdown timeout reached. running hard shutdown")
|
||||
err := s.srv.Stop()
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msg("error stopping reva server")
|
||||
}
|
||||
return nil
|
||||
case <-done:
|
||||
s.log.Info().Msg("reva server gracefully stopped")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// newServer runs a revad server w/o watcher with the given config file and options.
|
||||
func newServer(protocol int, mainConf map[string]interface{}, options Options) (*server, error) {
|
||||
parseSharedConfOrDie(mainConf["shared"])
|
||||
coreConf := parseCoreConfOrDie(mainConf["core"])
|
||||
|
||||
if err := registry.Init(options.Registry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv := &server{}
|
||||
|
||||
// update logger with hostname
|
||||
host, _ := os.Hostname()
|
||||
logger := options.Logger.With().Str("host.name", host).Logger()
|
||||
srv.log = &logger
|
||||
|
||||
// Only initialize tracing if we didn't get a tracer provider.
|
||||
if options.TraceProvider == nil {
|
||||
srv.log.Debug().Msg("no pre-existing tracer given, initializing tracing")
|
||||
options.TraceProvider = initTracing(coreConf)
|
||||
}
|
||||
initCPUCount(coreConf, srv.log)
|
||||
|
||||
srv.gracefulShutdownTimeout = 20 * time.Second
|
||||
if coreConf.GracefulShutdownTimeout > 0 {
|
||||
srv.gracefulShutdownTimeout = time.Duration(coreConf.GracefulShutdownTimeout) * time.Second
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case HTTP:
|
||||
s, err := getHTTPServer(mainConf["http"], srv.log, options.TraceProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv.srv = s
|
||||
srv.protocol = "http"
|
||||
// update logger with protocol
|
||||
logger := srv.log.With().Str("protocol", "http").Logger()
|
||||
srv.log = &logger
|
||||
return srv, nil
|
||||
case GRPC:
|
||||
s, err := getGRPCServer(mainConf["grpc"], srv.log, options.TraceProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv.srv = s
|
||||
srv.protocol = "grpc"
|
||||
// update logger with protocol
|
||||
logger := srv.log.With().Str("protocol", "grpc").Logger()
|
||||
srv.log = &logger
|
||||
return srv, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown protocol: %d", protocol)
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// Copyright 2018-2021 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
// These are all the extensions points for REVA
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/interceptors/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/credential/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/token/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/tokenwriter/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/http/interceptors/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/http/services/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/app/provider/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/app/registry/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/appauth/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/auth/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/auth/registry/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/datatx/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/group/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/metrics/driver/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/ocm/invite/repository/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/ocm/provider/authorizer/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/ocm/share/repository/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/permission/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/preferences/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/rhttp/datatx/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/share/cache/warmup/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/share/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/storage/fs/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/storage/registry/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/tenant/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/token/manager/loader"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/user/manager/loader"
|
||||
)
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// Copyright 2018-2021 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
"go-micro.dev/v4/registry"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger *zerolog.Logger
|
||||
Registry registry.Registry
|
||||
TraceProvider trace.TracerProvider
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithLogger provides a function to set the logger option.
|
||||
func WithLogger(logger *zerolog.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// WithRegistry provides a function to set the registry.
|
||||
func WithRegistry(r registry.Registry) Option {
|
||||
return func(o *Options) {
|
||||
o.Registry = r
|
||||
}
|
||||
}
|
||||
|
||||
// WithTraceProvider provides a function to set the trace provider.
|
||||
func WithTraceProvider(tp trace.TracerProvider) Option {
|
||||
return func(o *Options) {
|
||||
o.TraceProvider = tp
|
||||
}
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
// Copyright 2018-2021 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/logger"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
rtrace "github.com/opencloud-eu/reva/v2/pkg/trace"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Run runs a reva server with the given config file and pid file.
|
||||
func Run(mainConf map[string]interface{}, pidFile, logLevel string) {
|
||||
log := logger.InitLoggerOrDie(mainConf["log"], logLevel)
|
||||
RunWithOptions(mainConf, pidFile, WithLogger(log))
|
||||
}
|
||||
|
||||
// RunWithOptions runs a reva server with the given config file, pid file and options.
|
||||
func RunWithOptions(mainConf map[string]interface{}, pidFile string, opts ...Option) {
|
||||
options := newOptions(opts...)
|
||||
parseSharedConfOrDie(mainConf["shared"])
|
||||
coreConf := parseCoreConfOrDie(mainConf["core"])
|
||||
|
||||
if err := registry.Init(options.Registry); err != nil {
|
||||
options.Logger.Fatal().Err(err).Msg("failed to initialize registry client")
|
||||
return
|
||||
}
|
||||
|
||||
run(mainConf, coreConf, options.Logger, options.TraceProvider, pidFile)
|
||||
}
|
||||
|
||||
type coreConf struct {
|
||||
MaxCPUs string `mapstructure:"max_cpus"`
|
||||
TracesExporter string `mapstructure:"traces_exporter"`
|
||||
TracingServiceName string `mapstructure:"tracing_service_name"`
|
||||
|
||||
GracefulShutdownTimeout int `mapstructure:"graceful_shutdown_timeout"`
|
||||
}
|
||||
|
||||
func run(
|
||||
mainConf map[string]interface{},
|
||||
coreConf *coreConf,
|
||||
logger *zerolog.Logger,
|
||||
tp trace.TracerProvider,
|
||||
filename string,
|
||||
) {
|
||||
host, _ := os.Hostname()
|
||||
logger.Info().Msgf("host info: %s", host)
|
||||
|
||||
// Only initialise tracing if we didn't get a tracer provider.
|
||||
if tp == nil {
|
||||
logger.Debug().Msg("No pre-existing tracer given, initializing tracing")
|
||||
tp = initTracing(coreConf)
|
||||
}
|
||||
initCPUCount(coreConf, logger)
|
||||
|
||||
servers := initServers(mainConf, logger, tp)
|
||||
watcher, err := initWatcher(logger, filename, coreConf.GracefulShutdownTimeout)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
listeners := initListeners(watcher, servers, logger)
|
||||
|
||||
start(mainConf, servers, listeners, logger, watcher)
|
||||
}
|
||||
|
||||
func initListeners(watcher *grace.Watcher, servers map[string]grace.Server, log *zerolog.Logger) map[string]net.Listener {
|
||||
listeners, err := watcher.GetListeners(servers)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting sockets")
|
||||
watcher.Exit(1)
|
||||
}
|
||||
return listeners
|
||||
}
|
||||
|
||||
func initWatcher(log *zerolog.Logger, filename string, gracefulShutdownTimeout int) (*grace.Watcher, error) {
|
||||
watcher, err := handlePIDFlag(log, filename, gracefulShutdownTimeout)
|
||||
// TODO(labkode): maybe pidfile can be created later on? like once a server is going to be created?
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating grace watcher")
|
||||
os.Exit(1)
|
||||
}
|
||||
return watcher, err
|
||||
}
|
||||
|
||||
func initServers(mainConf map[string]interface{}, log *zerolog.Logger, tp trace.TracerProvider) map[string]grace.Server {
|
||||
servers := map[string]grace.Server{}
|
||||
if isEnabledHTTP(mainConf) {
|
||||
s, err := getHTTPServer(mainConf["http"], log, tp)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating http server")
|
||||
os.Exit(1)
|
||||
}
|
||||
servers["http"] = s
|
||||
}
|
||||
|
||||
if isEnabledGRPC(mainConf) {
|
||||
s, err := getGRPCServer(mainConf["grpc"], log, tp)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating grpc server")
|
||||
os.Exit(1)
|
||||
}
|
||||
servers["grpc"] = s
|
||||
}
|
||||
|
||||
if len(servers) == 0 {
|
||||
log.Info().Msg("nothing to do, no grpc/http enabled_services declared in config")
|
||||
os.Exit(1)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func initTracing(conf *coreConf) trace.TracerProvider {
|
||||
if conf.TracesExporter != "none" && conf.TracesExporter != "" {
|
||||
tp := rtrace.NewTracerProvider(conf.TracingServiceName, conf.TracesExporter)
|
||||
rtrace.SetDefaultTracerProvider(tp)
|
||||
return tp
|
||||
}
|
||||
return rtrace.DefaultProvider()
|
||||
}
|
||||
|
||||
func initCPUCount(conf *coreConf, log *zerolog.Logger) {
|
||||
ncpus, err := adjustCPU(conf.MaxCPUs)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error adjusting number of cpus")
|
||||
os.Exit(1)
|
||||
}
|
||||
// log.Info().Msgf("%s", getVersionString())
|
||||
log.Info().Msgf("running on %d cpus", ncpus)
|
||||
}
|
||||
|
||||
func handlePIDFlag(l *zerolog.Logger, pidFile string, gracefulShutdownTimeout int) (*grace.Watcher, error) {
|
||||
w := grace.NewWatcher(grace.WithPIDFile(pidFile),
|
||||
grace.WithLogger(l.With().Str("pkg", "grace").Logger()),
|
||||
grace.WithGracefuleShutdownTimeout(gracefulShutdownTimeout),
|
||||
)
|
||||
err := w.WritePID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func start(mainConf map[string]interface{}, servers map[string]grace.Server, listeners map[string]net.Listener, log *zerolog.Logger, watcher *grace.Watcher) {
|
||||
if isEnabledHTTP(mainConf) {
|
||||
go func() {
|
||||
if err := servers["http"].(*rhttp.Server).Start(listeners["http"]); err != nil {
|
||||
log.Error().Err(err).Msg("error starting the http server")
|
||||
watcher.Exit(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if isEnabledGRPC(mainConf) {
|
||||
go func() {
|
||||
if err := servers["grpc"].(*rgrpc.Server).Start(listeners["grpc"]); err != nil {
|
||||
log.Error().Err(err).Msg("error starting the grpc server")
|
||||
watcher.Exit(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
watcher.TrapSignals()
|
||||
}
|
||||
|
||||
func getGRPCServer(conf interface{}, l *zerolog.Logger, tp trace.TracerProvider) (*rgrpc.Server, error) {
|
||||
sub := l.With().Str("pkg", "rgrpc").Logger()
|
||||
s, err := rgrpc.NewServer(conf, sub, tp)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "main: error creating grpc server")
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func getHTTPServer(conf interface{}, l *zerolog.Logger, tp trace.TracerProvider) (*rhttp.Server, error) {
|
||||
sub := l.With().Str("pkg", "rhttp").Logger()
|
||||
s, err := rhttp.New(conf, sub, tp)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "main: error creating http server")
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// adjustCPU parses string cpu and sets GOMAXPROCS
|
||||
// according to its value. It accepts either
|
||||
// a number (e.g. 3) or a percent (e.g. 50%).
|
||||
// Default is to use all available cores.
|
||||
func adjustCPU(cpu string) (int, error) {
|
||||
var numCPU int
|
||||
|
||||
availCPU := runtime.NumCPU()
|
||||
|
||||
if cpu != "" {
|
||||
if strings.HasSuffix(cpu, "%") {
|
||||
// Percent
|
||||
var percent float32
|
||||
pctStr := cpu[:len(cpu)-1]
|
||||
pctInt, err := strconv.Atoi(pctStr)
|
||||
if err != nil || pctInt < 1 || pctInt > 100 {
|
||||
return 0, fmt.Errorf("invalid CPU value: percentage must be between 1-100")
|
||||
}
|
||||
percent = float32(pctInt) / 100
|
||||
numCPU = int(float32(availCPU) * percent)
|
||||
} else {
|
||||
// Number
|
||||
num, err := strconv.Atoi(cpu)
|
||||
if err != nil || num < 1 {
|
||||
return 0, fmt.Errorf("invalid CPU value: provide a number or percent greater than 0")
|
||||
}
|
||||
numCPU = num
|
||||
}
|
||||
} else {
|
||||
numCPU = availCPU
|
||||
}
|
||||
|
||||
if numCPU > availCPU || numCPU == 0 {
|
||||
numCPU = availCPU
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(numCPU)
|
||||
return numCPU, nil
|
||||
}
|
||||
|
||||
func parseCoreConfOrDie(v interface{}) *coreConf {
|
||||
c := &coreConf{}
|
||||
if err := mapstructure.Decode(v, c); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error decoding core config: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// tracing defaults to enabled if not explicitly configured
|
||||
if v == nil {
|
||||
c.TracesExporter = "console"
|
||||
} else if _, ok := v.(map[string]interface{})["traces_exporter"]; !ok {
|
||||
c.TracesExporter = "console"
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func parseSharedConfOrDie(v interface{}) {
|
||||
if err := sharedconf.Decode(v); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error decoding shared config: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func isEnabledHTTP(conf map[string]interface{}) bool {
|
||||
return isEnabled("http", conf)
|
||||
}
|
||||
|
||||
func isEnabledGRPC(conf map[string]interface{}) bool {
|
||||
return isEnabled("grpc", conf)
|
||||
}
|
||||
|
||||
func isEnabled(key string, conf map[string]interface{}) bool {
|
||||
if a, ok := conf[key]; ok {
|
||||
if b, ok := a.(map[string]interface{}); ok {
|
||||
if c, ok := b["services"]; ok {
|
||||
if d, ok := c.(map[string]interface{}); ok {
|
||||
if len(d) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user