github.com/cs3org/reva/v2@v2.27.7/cmd/revad/runtime/runtime.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package runtime 20 21 import ( 22 "fmt" 23 "log" 24 "net" 25 "os" 26 "runtime" 27 "strconv" 28 "strings" 29 30 "github.com/cs3org/reva/v2/cmd/revad/internal/grace" 31 "github.com/cs3org/reva/v2/pkg/logger" 32 "github.com/cs3org/reva/v2/pkg/registry" 33 "github.com/cs3org/reva/v2/pkg/rgrpc" 34 "github.com/cs3org/reva/v2/pkg/rhttp" 35 "github.com/cs3org/reva/v2/pkg/sharedconf" 36 rtrace "github.com/cs3org/reva/v2/pkg/trace" 37 "github.com/mitchellh/mapstructure" 38 "github.com/pkg/errors" 39 "github.com/rs/zerolog" 40 "go.opentelemetry.io/otel/trace" 41 ) 42 43 // Run runs a reva server with the given config file and pid file. 44 func Run(mainConf map[string]interface{}, pidFile, logLevel string) { 45 log := logger.InitLoggerOrDie(mainConf["log"], logLevel) 46 RunWithOptions(mainConf, pidFile, WithLogger(log)) 47 } 48 49 // RunWithOptions runs a reva server with the given config file, pid file and options. 50 func RunWithOptions(mainConf map[string]interface{}, pidFile string, opts ...Option) { 51 options := newOptions(opts...) 52 parseSharedConfOrDie(mainConf["shared"]) 53 coreConf := parseCoreConfOrDie(mainConf["core"]) 54 55 if err := registry.Init(options.Registry); err != nil { 56 panic(err) 57 } 58 59 run(mainConf, coreConf, options.Logger, options.TraceProvider, pidFile) 60 } 61 62 type coreConf struct { 63 MaxCPUs string `mapstructure:"max_cpus"` 64 TracingEnabled bool `mapstructure:"tracing_enabled"` 65 TracingInsecure bool `mapstructure:"tracing_insecure"` 66 TracingExporter string `mapstructure:"tracing_exporter"` 67 TracingEndpoint string `mapstructure:"tracing_endpoint"` 68 TracingCollector string `mapstructure:"tracing_collector"` 69 TracingServiceName string `mapstructure:"tracing_service_name"` 70 71 // TracingService specifies the service. i.e OpenCensus, OpenTelemetry, OpenTracing... 72 TracingService string `mapstructure:"tracing_service"` 73 74 GracefulShutdownTimeout int `mapstructure:"graceful_shutdown_timeout"` 75 } 76 77 func run( 78 mainConf map[string]interface{}, 79 coreConf *coreConf, 80 logger *zerolog.Logger, 81 tp trace.TracerProvider, 82 filename string, 83 ) { 84 host, _ := os.Hostname() 85 logger.Info().Msgf("host info: %s", host) 86 87 // Only initialise tracing if we didn't get a tracer provider. 88 if tp == nil { 89 logger.Debug().Msg("No pre-existing tracer given, initializing tracing") 90 tp = initTracing(coreConf) 91 } 92 initCPUCount(coreConf, logger) 93 94 servers := initServers(mainConf, logger, tp) 95 watcher, err := initWatcher(logger, filename, coreConf.GracefulShutdownTimeout) 96 if err != nil { 97 log.Panic(err) 98 } 99 listeners := initListeners(watcher, servers, logger) 100 101 start(mainConf, servers, listeners, logger, watcher) 102 } 103 104 func initListeners(watcher *grace.Watcher, servers map[string]grace.Server, log *zerolog.Logger) map[string]net.Listener { 105 listeners, err := watcher.GetListeners(servers) 106 if err != nil { 107 log.Error().Err(err).Msg("error getting sockets") 108 watcher.Exit(1) 109 } 110 return listeners 111 } 112 113 func initWatcher(log *zerolog.Logger, filename string, gracefulShutdownTimeout int) (*grace.Watcher, error) { 114 watcher, err := handlePIDFlag(log, filename, gracefulShutdownTimeout) 115 // TODO(labkode): maybe pidfile can be created later on? like once a server is going to be created? 116 if err != nil { 117 log.Error().Err(err).Msg("error creating grace watcher") 118 os.Exit(1) 119 } 120 return watcher, err 121 } 122 123 func initServers(mainConf map[string]interface{}, log *zerolog.Logger, tp trace.TracerProvider) map[string]grace.Server { 124 servers := map[string]grace.Server{} 125 if isEnabledHTTP(mainConf) { 126 s, err := getHTTPServer(mainConf["http"], log, tp) 127 if err != nil { 128 log.Error().Err(err).Msg("error creating http server") 129 os.Exit(1) 130 } 131 servers["http"] = s 132 } 133 134 if isEnabledGRPC(mainConf) { 135 s, err := getGRPCServer(mainConf["grpc"], log, tp) 136 if err != nil { 137 log.Error().Err(err).Msg("error creating grpc server") 138 os.Exit(1) 139 } 140 servers["grpc"] = s 141 } 142 143 if len(servers) == 0 { 144 log.Info().Msg("nothing to do, no grpc/http enabled_services declared in config") 145 os.Exit(1) 146 } 147 return servers 148 } 149 150 func initTracing(conf *coreConf) trace.TracerProvider { 151 if conf.TracingEnabled { 152 opts := []rtrace.Option{ 153 rtrace.WithExporter(conf.TracingExporter), 154 rtrace.WithEndpoint(conf.TracingEndpoint), 155 rtrace.WithCollector(conf.TracingCollector), 156 rtrace.WithServiceName(conf.TracingServiceName), 157 } 158 if conf.TracingEnabled { 159 opts = append(opts, rtrace.WithEnabled()) 160 } 161 if conf.TracingInsecure { 162 opts = append(opts, rtrace.WithInsecure()) 163 } 164 tp := rtrace.NewTracerProvider(opts...) 165 rtrace.SetDefaultTracerProvider(tp) 166 return tp 167 } 168 return rtrace.DefaultProvider() 169 } 170 171 func initCPUCount(conf *coreConf, log *zerolog.Logger) { 172 ncpus, err := adjustCPU(conf.MaxCPUs) 173 if err != nil { 174 log.Error().Err(err).Msg("error adjusting number of cpus") 175 os.Exit(1) 176 } 177 // log.Info().Msgf("%s", getVersionString()) 178 log.Info().Msgf("running on %d cpus", ncpus) 179 } 180 181 func handlePIDFlag(l *zerolog.Logger, pidFile string, gracefulShutdownTimeout int) (*grace.Watcher, error) { 182 w := grace.NewWatcher(grace.WithPIDFile(pidFile), 183 grace.WithLogger(l.With().Str("pkg", "grace").Logger()), 184 grace.WithGracefuleShutdownTimeout(gracefulShutdownTimeout), 185 ) 186 err := w.WritePID() 187 if err != nil { 188 return nil, err 189 } 190 191 return w, nil 192 } 193 194 func start(mainConf map[string]interface{}, servers map[string]grace.Server, listeners map[string]net.Listener, log *zerolog.Logger, watcher *grace.Watcher) { 195 if isEnabledHTTP(mainConf) { 196 go func() { 197 if err := servers["http"].(*rhttp.Server).Start(listeners["http"]); err != nil { 198 log.Error().Err(err).Msg("error starting the http server") 199 watcher.Exit(1) 200 } 201 }() 202 } 203 if isEnabledGRPC(mainConf) { 204 go func() { 205 if err := servers["grpc"].(*rgrpc.Server).Start(listeners["grpc"]); err != nil { 206 log.Error().Err(err).Msg("error starting the grpc server") 207 watcher.Exit(1) 208 } 209 }() 210 } 211 watcher.TrapSignals() 212 } 213 214 func getGRPCServer(conf interface{}, l *zerolog.Logger, tp trace.TracerProvider) (*rgrpc.Server, error) { 215 sub := l.With().Str("pkg", "rgrpc").Logger() 216 s, err := rgrpc.NewServer(conf, sub, tp) 217 if err != nil { 218 err = errors.Wrap(err, "main: error creating grpc server") 219 return nil, err 220 } 221 return s, nil 222 } 223 224 func getHTTPServer(conf interface{}, l *zerolog.Logger, tp trace.TracerProvider) (*rhttp.Server, error) { 225 sub := l.With().Str("pkg", "rhttp").Logger() 226 s, err := rhttp.New(conf, sub, tp) 227 if err != nil { 228 err = errors.Wrap(err, "main: error creating http server") 229 return nil, err 230 } 231 return s, nil 232 } 233 234 // adjustCPU parses string cpu and sets GOMAXPROCS 235 // according to its value. It accepts either 236 // a number (e.g. 3) or a percent (e.g. 50%). 237 // Default is to use all available cores. 238 func adjustCPU(cpu string) (int, error) { 239 var numCPU int 240 241 availCPU := runtime.NumCPU() 242 243 if cpu != "" { 244 if strings.HasSuffix(cpu, "%") { 245 // Percent 246 var percent float32 247 pctStr := cpu[:len(cpu)-1] 248 pctInt, err := strconv.Atoi(pctStr) 249 if err != nil || pctInt < 1 || pctInt > 100 { 250 return 0, fmt.Errorf("invalid CPU value: percentage must be between 1-100") 251 } 252 percent = float32(pctInt) / 100 253 numCPU = int(float32(availCPU) * percent) 254 } else { 255 // Number 256 num, err := strconv.Atoi(cpu) 257 if err != nil || num < 1 { 258 return 0, fmt.Errorf("invalid CPU value: provide a number or percent greater than 0") 259 } 260 numCPU = num 261 } 262 } else { 263 numCPU = availCPU 264 } 265 266 if numCPU > availCPU || numCPU == 0 { 267 numCPU = availCPU 268 } 269 270 runtime.GOMAXPROCS(numCPU) 271 return numCPU, nil 272 } 273 274 func parseCoreConfOrDie(v interface{}) *coreConf { 275 c := &coreConf{} 276 if err := mapstructure.Decode(v, c); err != nil { 277 fmt.Fprintf(os.Stderr, "error decoding core config: %s\n", err.Error()) 278 os.Exit(1) 279 } 280 281 // tracing defaults to enabled if not explicitly configured 282 if v == nil { 283 c.TracingEnabled = true 284 c.TracingEndpoint = "localhost:6831" 285 } else if _, ok := v.(map[string]interface{})["tracing_enabled"]; !ok { 286 c.TracingEnabled = true 287 c.TracingEndpoint = "localhost:6831" 288 } 289 290 return c 291 } 292 293 func parseSharedConfOrDie(v interface{}) { 294 if err := sharedconf.Decode(v); err != nil { 295 fmt.Fprintf(os.Stderr, "error decoding shared config: %s\n", err.Error()) 296 os.Exit(1) 297 } 298 } 299 300 func isEnabledHTTP(conf map[string]interface{}) bool { 301 return isEnabled("http", conf) 302 } 303 304 func isEnabledGRPC(conf map[string]interface{}) bool { 305 return isEnabled("grpc", conf) 306 } 307 308 func isEnabled(key string, conf map[string]interface{}) bool { 309 if a, ok := conf[key]; ok { 310 if b, ok := a.(map[string]interface{}); ok { 311 if c, ok := b["services"]; ok { 312 if d, ok := c.(map[string]interface{}); ok { 313 if len(d) > 0 { 314 return true 315 } 316 } 317 } 318 } 319 } 320 return false 321 }