featurebase/dax/service_manager.go
Matthew Jaffee 903e234c69
tweak a bunch of logging and config (#2234)
* tweak a bunch of logging and config

make overall logs less verbose and chatty

1 minute computer check-in interval

3 minute snapshot interval

remove CaptureLogger as it has same functionality as buffer logger

add a WithPrefix to the Logger interface so sub-services can have
different prefixes

* fix some lint

* fix lint... confused why this is coming up now
2023-02-03 14:59:07 -06:00

419 lines
9.2 KiB
Go

package dax
import (
"fmt"
"net/http"
"sync"
_ "net/http/pprof" // Imported for its side-effect of registering pprof endpoints with the server.
"github.com/featurebasedb/featurebase/v3/errors"
"github.com/featurebasedb/featurebase/v3/logger"
"github.com/felixge/fgprof"
"github.com/gorilla/mux"
)
// ServiceKey is a unique key used to identify one service managed by the
// ServiceManager. These typically align with the ServicePrefix* values.
type ServiceKey string
// ServiceManager manages the various services running in process. It is used to
// do things like start/stop services, and it dynamically builds the http router
// depending on the state of all services.
type ServiceManager struct {
mu sync.RWMutex
// Controller
Controller ControllerService
controllerStarted bool
// Queryer
Queryer QueryerService
queryerStarted bool
// Computers
computerID int
computers map[ServiceKey]*computerServiceState
drouter *dynamicRouter
Logger logger.Logger
}
type computerServiceState struct {
service ComputerService
started bool
}
// NewServiceManager returns a new ServiceManager with default values.
func NewServiceManager() *ServiceManager {
return &ServiceManager{
computers: map[ServiceKey]*computerServiceState{},
drouter: &dynamicRouter{},
Logger: logger.NopLogger,
}
}
// HTTPHandler returns the current http.Handler for ServiceManager based on the
// state of its services.
func (s *ServiceManager) HTTPHandler() http.Handler {
s.mu.RLock()
defer s.mu.RUnlock()
s.resetRouter()
return s.drouter
}
// StartAll starts all services which have been added to ServiceManager.
func (s *ServiceManager) StartAll() error {
// Controller
if err := s.ControllerStart(); err != nil {
return errors.Wrap(err, "starting controller")
}
// Queryer
if err := s.QueryerStart(); err != nil {
return errors.Wrap(err, "starting queryer")
}
// Computer(s)
for key := range s.computers {
if err := s.ComputerStart(key); err != nil {
return errors.Wrapf(err, "starting computer (%s)", key)
}
}
return nil
}
func (s *ServiceManager) StopAll() error {
for key := range s.computers {
if err := s.ComputerStop(key); err != nil {
s.Logger.Printf("stopping computer %s: %v", key, err)
}
}
if err := s.QueryerStop(); err != nil {
s.Logger.Printf("stopping queryer: %v", err)
}
return s.ControllerStop()
}
// ControllerStart starts the Controller service.
func (s *ServiceManager) ControllerStart() error {
if s.Controller == nil {
s.Logger.Debugf("Skipping Controller")
return nil
}
s.Logger.Printf("Starting Controller")
s.mu.Lock()
defer s.mu.Unlock()
if s.controllerStarted {
return nil
}
s.controllerStarted = true
s.resetRouter()
if err := s.Controller.Start(); err != nil {
s.controllerStarted = false
return errors.Wrap(err, "starting controller")
}
return nil
}
// ControllerStop stops the Controller service.
func (s *ServiceManager) ControllerStop() error {
if s.Controller == nil {
return nil
}
s.Logger.Printf("Stopping Controller")
s.mu.Lock()
defer s.mu.Unlock()
if !s.controllerStarted {
return nil
}
s.controllerStarted = false
s.resetRouter()
if err := s.Controller.Stop(); err != nil {
s.controllerStarted = true
return errors.Wrap(err, "stopping controller")
}
return nil
}
// QueryerStart starts the Queryer service.
func (s *ServiceManager) QueryerStart() error {
if s.Queryer == nil {
s.Logger.Debugf("Skipping Queryer")
return nil
}
s.Logger.Printf("Starting Queryer")
s.mu.Lock()
defer s.mu.Unlock()
if s.queryerStarted {
return nil
}
s.queryerStarted = true
s.resetRouter()
if err := s.Queryer.Start(); err != nil {
s.queryerStarted = false
return errors.Wrap(err, "starting queryer")
}
return nil
}
// QueryerStop stops the Queryer service.
func (s *ServiceManager) QueryerStop() error {
if s.Queryer == nil {
return nil
}
s.Logger.Printf("Stopping Queryer")
s.mu.Lock()
defer s.mu.Unlock()
if !s.queryerStarted {
return nil
}
s.queryerStarted = false
s.resetRouter()
if err := s.Queryer.Stop(); err != nil {
s.queryerStarted = true
return errors.Wrap(err, "stopping queryer")
}
return nil
}
// Computer returns the ComputerService specified by the provided key.
func (s *ServiceManager) Computer(key ServiceKey) ComputerService {
serviceState, ok := s.computers[key]
if !ok {
return nil
}
return serviceState.service
}
// ComputerStart starts the Computer service specified by the provided key.
func (s *ServiceManager) ComputerStart(key ServiceKey) error {
s.mu.Lock()
defer s.mu.Unlock()
s.Logger.Printf("Starting Computer: %s", key)
serviceState, ok := s.computers[key]
if !ok {
return errors.Errorf("computer to be started does not exist: %s", key)
}
if serviceState.started {
return nil
}
serviceState.started = true
if err := serviceState.service.Start(); err != nil {
serviceState.started = false
return errors.Wrapf(err, "starting computer (%s)", key)
}
// resetRouter is called *after* service.Start() for computer (but not other
// service types) because currently, the handler returned by
// server.Command.HTTPHandler() doesn't get initialized until startup. A
// task for the future will be to tease out the computer http routes so that
// they're available prior to startup.
s.resetRouter()
return nil
}
// ComputerStop stops the Computer service specified by the provided key.
func (s *ServiceManager) ComputerStop(key ServiceKey) error {
s.mu.Lock()
defer s.mu.Unlock()
s.Logger.Printf("Stopping Computer: %s", key)
serviceState, ok := s.computers[key]
if !ok {
return errors.Errorf("computer to be stopped does not exist: %s", key)
}
if !serviceState.started {
return nil
}
serviceState.started = false
s.resetRouter()
if err := serviceState.service.Stop(); err != nil {
serviceState.started = true
return errors.Wrapf(err, "stopping computer (%s)", key)
}
return nil
}
// Computers returns a map (keyed by ServiceKey) of all computers registered
// with ServiceManager.
func (s *ServiceManager) Computers() map[ServiceKey]ComputerService {
s.mu.RLock()
defer s.mu.RUnlock()
m := make(map[ServiceKey]ComputerService)
for k, v := range s.computers {
m[k] = v.service
}
return m
}
// AddComputer adds the provided ComputerService to ServiceManager. It assigns
// the service a unique ServiceKey.
func (s *ServiceManager) AddComputer(cs ComputerService) ServiceKey {
s.mu.Lock()
defer s.mu.Unlock()
key := ServiceKey(fmt.Sprintf("%s%d", ServicePrefixComputer, s.computerID))
s.computers[key] = &computerServiceState{
service: cs,
}
cs.SetKey(key)
s.computerID++
return key
}
// RemoveComputer removes the ComputerService specified by the provided key.
func (s *ServiceManager) RemoveComputer(key ServiceKey) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.computers[key]; ok {
delete(s.computers, key)
s.resetRouter()
return true
}
return false
}
func getHealth(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
}
// Must be called with at least a read lock held (because that's required of buildRouter).
func (s *ServiceManager) resetRouter() {
s.drouter.Swap(s.buildRouter())
}
// Must be called with at least a read lock held?
func (s *ServiceManager) buildRouter() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/health", getHealth).Methods("GET").Name("GetHealth")
router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux).Methods("GET")
router.PathPrefix("/debug/fgprof").Handler(fgprof.Handler()).Methods("GET")
// Controller.
if s.Controller != nil && s.controllerStarted {
pre := "/" + ServicePrefixController
router.PathPrefix(pre + "/").Handler(
http.StripPrefix(pre, s.Controller.HTTPHandler()))
}
// Computers.
for k, serviceState := range s.computers {
// Skip any computer which have not been started.
if !serviceState.started {
continue
}
pre := "/" + string(k)
router.PathPrefix(pre + "/").Handler(
http.StripPrefix(pre, serviceState.service.HTTPHandler()))
}
// Queryer.
if s.Queryer != nil {
pre := "/" + ServicePrefixQueryer
router.PathPrefix(pre + "/").Handler(
http.StripPrefix(pre, s.Queryer.HTTPHandler()))
}
return router
}
//////////////////////////////////////////
// Service is an interface implemented by any service which is part of
// ServiceManager.
type Service interface {
Start() error
Stop() error
Address() Address
HTTPHandler() http.Handler
}
// MultiService is a service type which can have multiple instances within
// ServicesManager.
type MultiService interface {
Service
SetKey(ServiceKey)
Key() ServiceKey
}
type ControllerService interface {
Service
}
type ComputerService interface {
MultiService
SetController(Address) error
}
type QueryerService interface {
Service
SetController(Address) error
}
//////////////////////////////////////////
// dynamicRouter is used to dynamically swap out http routers as service states
// withing ServiceManager change.
type dynamicRouter struct {
mu sync.RWMutex
router *mux.Router
}
func (dr *dynamicRouter) Swap(new *mux.Router) {
dr.mu.Lock()
defer dr.mu.Unlock()
dr.router = new
}
func (dr *dynamicRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
dr.mu.RLock()
router := dr.router
dr.mu.RUnlock()
router.ServeHTTP(w, r)
}