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) }