Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -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
}
@@ -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)
}
@@ -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"
)
@@ -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
}
}
@@ -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
}