github.com/m3db/m3@v1.5.0/src/aggregator/server/server.go (about)

     1  // Copyright (c) 2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package server
    22  
    23  import (
    24  	"log"
    25  	"net/http"
    26  	"os"
    27  	"os/signal"
    28  	"syscall"
    29  	"time"
    30  
    31  	m3aggregator "github.com/m3db/m3/src/aggregator/aggregator"
    32  	"github.com/m3db/m3/src/cmd/services/m3aggregator/config"
    33  	"github.com/m3db/m3/src/cmd/services/m3aggregator/serve"
    34  	"github.com/m3db/m3/src/x/clock"
    35  	xconfig "github.com/m3db/m3/src/x/config"
    36  	"github.com/m3db/m3/src/x/instrument"
    37  	xos "github.com/m3db/m3/src/x/os"
    38  
    39  	"go.uber.org/zap"
    40  )
    41  
    42  const (
    43  	gracefulShutdownTimeout = 15 * time.Second
    44  )
    45  
    46  // RunOptions are the server options for running the aggregator server.
    47  type RunOptions struct {
    48  	// Config is the aggregator configuration.
    49  	Config config.Configuration
    50  
    51  	// AdminOptions are additional options to apply to the aggregator server.
    52  	AdminOptions []AdminOption
    53  
    54  	// CustomBuildTags are additional tags to be added to the instrument build
    55  	// reporter.
    56  	CustomBuildTags map[string]string
    57  
    58  	// InterruptCh is a programmatic interrupt channel to supply to
    59  	// interrupt and shutdown the server.
    60  	InterruptCh <-chan error
    61  
    62  	// ShutdownCh is an optional channel to supply if interested in receiving
    63  	// a notification that the server has shutdown.
    64  	ShutdownCh chan<- struct{}
    65  }
    66  
    67  // AdminOption is an additional option to apply to the aggregator server.
    68  type AdminOption func(opts serve.Options) (serve.Options, error)
    69  
    70  // Run runs the aggregator server.
    71  func Run(opts RunOptions) {
    72  	cfg := opts.Config
    73  
    74  	// Create logger and metrics scope.
    75  	logger, err := cfg.LoggingOrDefault().BuildLogger()
    76  	if err != nil {
    77  		log.Fatalf("error creating logger: %v", err)
    78  	}
    79  
    80  	// NB(nate): Register shutdown notification defer function first so that
    81  	// it's the last defer to fire before terminating. This allows other defer methods
    82  	// that clean up resources to execute first.
    83  	if opts.ShutdownCh != nil {
    84  		defer func() {
    85  			select {
    86  			case opts.ShutdownCh <- struct{}{}:
    87  				break
    88  			default:
    89  				logger.Warn("could not send shutdown notification as channel was full")
    90  			}
    91  		}()
    92  	}
    93  
    94  	defer logger.Sync()
    95  
    96  	cfg.Debug.SetRuntimeValues(logger)
    97  
    98  	xconfig.WarnOnDeprecation(cfg, logger)
    99  
   100  	defaultServeMux := http.NewServeMux()
   101  	metricsCfg := cfg.MetricsOrDefault()
   102  	scope, closer, _, err := metricsCfg.NewRootScopeAndReporters(
   103  		instrument.NewRootScopeAndReportersOptions{
   104  			PrometheusDefaultServeMux: defaultServeMux,
   105  		})
   106  	if err != nil {
   107  		logger.Fatal("error creating metrics root scope", zap.Error(err))
   108  	}
   109  	defer closer.Close()
   110  	instrumentOpts := instrument.NewOptions().
   111  		SetLogger(logger).
   112  		SetMetricsScope(scope).
   113  		SetTimerOptions(instrument.TimerOptions{StandardSampleRate: metricsCfg.SampleRate()}).
   114  		SetReportInterval(metricsCfg.ReportInterval()).
   115  		SetCustomBuildTags(opts.CustomBuildTags)
   116  
   117  	buildReporter := instrument.NewBuildReporter(instrumentOpts)
   118  	if err := buildReporter.Start(); err != nil {
   119  		logger.Fatal("could not start build reporter", zap.Error(err))
   120  	}
   121  
   122  	defer buildReporter.Stop()
   123  
   124  	if cfg.M3Msg == nil && cfg.RawTCP == nil {
   125  		m3MsgCfg := cfg.M3MsgOrDefault()
   126  		cfg.M3Msg = &m3MsgCfg
   127  	}
   128  
   129  	serverOptions := serve.NewOptions(instrumentOpts)
   130  	if cfg.M3Msg != nil {
   131  		// Create the M3Msg server options.
   132  		m3msgInsrumentOpts := instrumentOpts.
   133  			SetMetricsScope(scope.
   134  				SubScope("m3msg-server").
   135  				Tagged(map[string]string{"server": "m3msg"}))
   136  		m3msgServerOpts, err := cfg.M3Msg.NewServerOptions(m3msgInsrumentOpts)
   137  		if err != nil {
   138  			logger.Fatal("could not create m3msg server options", zap.Error(err))
   139  		}
   140  
   141  		serverOptions = serverOptions.
   142  			SetM3MsgAddr(cfg.M3Msg.Server.ListenAddress).
   143  			SetM3MsgServerOpts(m3msgServerOpts)
   144  	}
   145  
   146  	if cfg.RawTCP != nil {
   147  		// Create the raw TCP server options.
   148  		rawTCPInstrumentOpts := instrumentOpts.
   149  			SetMetricsScope(scope.
   150  				SubScope("rawtcp-server").
   151  				Tagged(map[string]string{"server": "rawtcp"}))
   152  
   153  		serverOptions = serverOptions.
   154  			SetRawTCPAddr(cfg.RawTCP.ListenAddress).
   155  			SetRawTCPServerOpts(cfg.RawTCP.NewServerOptions(rawTCPInstrumentOpts))
   156  	}
   157  
   158  	// Create the http server options.
   159  	httpCfg := cfg.HTTPOrDefault()
   160  	serverOptions = serverOptions.
   161  		SetHTTPAddr(httpCfg.ListenAddress).
   162  		SetHTTPServerOpts(httpCfg.NewServerOptions().SetMux(defaultServeMux))
   163  
   164  	for i, transform := range opts.AdminOptions {
   165  		if opts, err := transform(serverOptions); err != nil {
   166  			logger.Fatal("could not apply transform",
   167  				zap.Int("index", i), zap.Error(err))
   168  		} else {
   169  			serverOptions = opts
   170  		}
   171  	}
   172  
   173  	// Create the kv client.
   174  	kvCfg := cfg.KVClientOrDefault()
   175  	client, err := kvCfg.NewKVClient(instrumentOpts.
   176  		SetMetricsScope(scope.SubScope("kv-client")))
   177  	if err != nil {
   178  		logger.Fatal("error creating the kv client", zap.Error(err))
   179  	}
   180  
   181  	// Create the runtime options manager.
   182  	runtimeCfg := cfg.RuntimeOptionsOrDefault()
   183  	runtimeOptsManager := runtimeCfg.NewRuntimeOptionsManager()
   184  
   185  	// Create the aggregator.
   186  	aggCfg := cfg.AggregatorOrDefault()
   187  	aggregatorOpts, err := aggCfg.NewAggregatorOptions(
   188  		serverOptions.RawTCPAddr(),
   189  		client, serverOptions, runtimeOptsManager, clock.NewOptions(),
   190  		instrumentOpts.SetMetricsScope(scope.SubScope("aggregator")))
   191  	if err != nil {
   192  		logger.Fatal("error creating aggregator options", zap.Error(err))
   193  	}
   194  	aggregator := m3aggregator.NewAggregator(aggregatorOpts)
   195  	if err := aggregator.Open(); err != nil {
   196  		logger.Fatal("error opening the aggregator", zap.Error(err))
   197  	}
   198  
   199  	// Watch runtime option changes after aggregator is open.
   200  	placementManager := aggregatorOpts.PlacementManager()
   201  	runtimeCfg.WatchRuntimeOptionChanges(client, runtimeOptsManager, placementManager, logger)
   202  
   203  	doneCh := make(chan struct{})
   204  	closedCh := make(chan struct{})
   205  	go func() {
   206  		if err := serve.Serve(
   207  			aggregator,
   208  			doneCh,
   209  			serverOptions,
   210  		); err != nil {
   211  			logger.Fatal("could not start serving traffic", zap.Error(err))
   212  		}
   213  		logger.Debug("server closed")
   214  		close(closedCh)
   215  	}()
   216  
   217  	// Handle interrupts.
   218  	xos.WaitForInterrupt(logger, xos.InterruptOptions{
   219  		InterruptCh: opts.InterruptCh,
   220  	})
   221  
   222  	if s := aggCfg.ShutdownWaitTimeout; s != 0 {
   223  		sigC := make(chan os.Signal, 1)
   224  		signal.Notify(sigC, syscall.SIGINT, syscall.SIGTERM)
   225  
   226  		logger.Info("waiting intentional shutdown period", zap.Duration("waitTimeout", s))
   227  		select {
   228  		case sig := <-sigC:
   229  			logger.Info("second signal received, skipping shutdown wait", zap.String("signal", sig.String()))
   230  		case <-time.After(aggCfg.ShutdownWaitTimeout):
   231  			logger.Info("shutdown period elapsed")
   232  		}
   233  	}
   234  
   235  	close(doneCh)
   236  
   237  	select {
   238  	case <-closedCh:
   239  		logger.Info("server closed clean")
   240  	case <-time.After(gracefulShutdownTimeout):
   241  		logger.Info("server closed due to timeout", zap.Duration("timeout", gracefulShutdownTimeout))
   242  		scope.SubScope("aggregator-server-close").Counter("timeout").Inc(1)
   243  	}
   244  }