/* Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments Copyright (C) ITsysCOM GmbH This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ package servmanager import ( "errors" "fmt" "reflect" "strings" "sync" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" "github.com/cgrates/rpcclient" ) // NewServiceManager returns a service manager func NewServiceManager(cfg *config.CGRConfig, dm *engine.DataManager, cdrStorage engine.CdrStorage, loadStorage engine.LoadStorage, filterSChan chan *engine.FilterS, server *utils.Server, dispatcherSChan chan rpcclient.RpcClientConnection, engineShutdown chan bool) *ServiceManager { sm := &ServiceManager{ cfg: cfg, dm: dm, engineShutdown: engineShutdown, cdrStorage: cdrStorage, loadStorage: loadStorage, filterS: filterSChan, server: server, subsystems: make(map[string]Service), dispatcherSChan: dispatcherSChan, } return sm } // ServiceManager handles service management ran by the engine type ServiceManager struct { sync.RWMutex // lock access to any shared data cfg *config.CGRConfig dm *engine.DataManager engineShutdown chan bool cacheS *engine.CacheS cdrStorage engine.CdrStorage loadStorage engine.LoadStorage filterS chan *engine.FilterS server *utils.Server subsystems map[string]Service dispatcherSChan chan rpcclient.RpcClientConnection } func (srvMngr *ServiceManager) Call(serviceMethod string, args interface{}, reply interface{}) error { parts := strings.Split(serviceMethod, ".") if len(parts) != 2 { return rpcclient.ErrUnsupporteServiceMethod } // get method method := reflect.ValueOf(srvMngr).MethodByName(parts[0][len(parts[0])-2:] + parts[1]) // Inherit the version in the method if !method.IsValid() { return rpcclient.ErrUnsupporteServiceMethod } // construct the params params := []reflect.Value{reflect.ValueOf(args), reflect.ValueOf(reply)} ret := method.Call(params) if len(ret) != 1 { return utils.ErrServerError } if ret[0].Interface() == nil { return nil } err, ok := ret[0].Interface().(error) if !ok { return utils.ErrServerError } return err } // ArgShutdownService are passed to Start/StopService/Status RPC methods type ArgStartService struct { ServiceID string } // ShutdownService shuts-down a service with ID func (srvMngr *ServiceManager) V1StartService(args ArgStartService, reply *string) (err error) { switch args.ServiceID { case utils.MetaScheduler: // stop the service using the config srvMngr.Lock() srvMngr.cfg.SchedulerCfg().Enabled = true srvMngr.Unlock() srvMngr.cfg.GetReloadChan(config.SCHEDULER_JSN) <- struct{}{} default: err = errors.New(utils.UnsupportedServiceIDCaps) } if err != nil { return err } *reply = utils.OK return } // ShutdownService shuts-down a service with ID func (srvMngr *ServiceManager) V1StopService(args ArgStartService, reply *string) (err error) { switch args.ServiceID { case utils.MetaScheduler: // stop the service using the config srvMngr.Lock() srvMngr.cfg.SchedulerCfg().Enabled = false srvMngr.Unlock() srvMngr.cfg.GetReloadChan(config.SCHEDULER_JSN) <- struct{}{} default: err = errors.New(utils.UnsupportedServiceIDCaps) } if err != nil { return err } *reply = utils.OK return } // ShutdownService shuts-down a service with ID func (srvMngr *ServiceManager) V1ServiceStatus(args ArgStartService, reply *string) error { srvMngr.RLock() defer srvMngr.RUnlock() var running bool switch args.ServiceID { case utils.MetaScheduler: running = srvMngr.subsystems[utils.SchedulerS].IsRunning() default: return errors.New(utils.UnsupportedServiceIDCaps) } if running { *reply = utils.RunningCaps } else { *reply = utils.StoppedCaps } return nil } // GetDM returns the DataManager func (srvMngr *ServiceManager) GetDM() *engine.DataManager { srvMngr.RLock() defer srvMngr.RUnlock() return srvMngr.dm } // GetCDRStorage returns the CdrStorage func (srvMngr *ServiceManager) GetCDRStorage() engine.CdrStorage { srvMngr.RLock() defer srvMngr.RUnlock() return srvMngr.cdrStorage } // GetLoadStorage returns the LoadStorage func (srvMngr *ServiceManager) GetLoadStorage() engine.LoadStorage { srvMngr.RLock() defer srvMngr.RUnlock() return srvMngr.loadStorage } // GetConfig returns the Configuration func (srvMngr *ServiceManager) GetConfig() *config.CGRConfig { srvMngr.RLock() defer srvMngr.RUnlock() return srvMngr.cfg } // GetCacheS returns the CacheS func (srvMngr *ServiceManager) GetCacheS() *engine.CacheS { srvMngr.RLock() defer srvMngr.RUnlock() return srvMngr.cacheS } // GetFilterS returns the FilterS func (srvMngr *ServiceManager) GetFilterS() (fS *engine.FilterS) { srvMngr.RLock() defer srvMngr.RUnlock() fS = <-srvMngr.filterS srvMngr.filterS <- fS return } // GetServer returns the Server func (srvMngr *ServiceManager) GetServer() *utils.Server { srvMngr.RLock() defer srvMngr.RUnlock() return srvMngr.server } // GetExitChan returns the exit chanel func (srvMngr *ServiceManager) GetExitChan() chan bool { return srvMngr.engineShutdown } // GetConnection creates a rpcClient to the specified subsystem func (srvMngr *ServiceManager) GetConnection(subsystem string, conns []*config.RemoteHost) (rpcclient.RpcClientConnection, error) { if len(conns) == 0 { return nil, nil } service, has := srvMngr.GetService(subsystem) if !has { // used to not cause panics because of services that are not already migrated return nil, errors.New(utils.UnsupportedServiceIDCaps) } internalChan := service.GetIntenternalChan() if srvMngr.GetConfig().DispatcherSCfg().Enabled { internalChan = srvMngr.dispatcherSChan } return engine.NewRPCPool(rpcclient.POOL_FIRST, srvMngr.GetConfig().TlsCfg().ClientKey, srvMngr.GetConfig().TlsCfg().ClientCerificate, srvMngr.GetConfig().TlsCfg().CaCertificate, srvMngr.GetConfig().GeneralCfg().ConnectAttempts, srvMngr.GetConfig().GeneralCfg().Reconnects, srvMngr.GetConfig().GeneralCfg().ConnectTimeout, srvMngr.GetConfig().GeneralCfg().ReplyTimeout, conns, internalChan, false) } // StartServices starts all enabled services func (srvMngr *ServiceManager) StartServices() (err error) { // start the cacheS if srvMngr.GetCacheS() == nil { chS, has := srvMngr.GetService(utils.CacheS) if !has { utils.Logger.Err(fmt.Sprintf("<%s> Failed to find needed subsystem <%s>", utils.ServiceManager, utils.CacheS)) return } chS.Start(srvMngr, true) } go srvMngr.handleReload() if srvMngr.GetConfig().AttributeSCfg().Enabled { go srvMngr.startService(utils.AttributeS) } if srvMngr.GetConfig().ChargerSCfg().Enabled { go srvMngr.startService(utils.ChargerS) } if srvMngr.GetConfig().ThresholdSCfg().Enabled { go srvMngr.startService(utils.ThresholdS) } if srvMngr.GetConfig().StatSCfg().Enabled { go srvMngr.startService(utils.StatS) } if srvMngr.GetConfig().ResourceSCfg().Enabled { go srvMngr.startService(utils.ResourceS) } if srvMngr.GetConfig().SupplierSCfg().Enabled { go srvMngr.startService(utils.SupplierS) } if srvMngr.GetConfig().SchedulerCfg().Enabled { go srvMngr.startService(utils.SchedulerS) } if srvMngr.GetConfig().CdrsCfg().Enabled { go srvMngr.startService(utils.CDRServer) } if srvMngr.GetConfig().RalsCfg().RALsEnabled { go srvMngr.startService(utils.RALService) } // startServer() return } // AddService adds given services func (srvMngr *ServiceManager) AddService(services ...Service) { srvMngr.Lock() for _, srv := range services { if _, has := srvMngr.subsystems[srv.ServiceName()]; has { // do not rewrite the service continue } srvMngr.subsystems[srv.ServiceName()] = srv } srvMngr.Unlock() } func (srvMngr *ServiceManager) handleReload() { var err error for { select { case ext := <-srvMngr.engineShutdown: srvMngr.engineShutdown <- ext return case <-srvMngr.GetConfig().GetReloadChan(config.ATTRIBUTE_JSN): if err = srvMngr.reloadService(utils.AttributeS, srvMngr.GetConfig().AttributeSCfg().Enabled); err != nil { return } case <-srvMngr.GetConfig().GetReloadChan(config.ChargerSCfgJson): if err = srvMngr.reloadService(utils.ChargerS, srvMngr.GetConfig().ChargerSCfg().Enabled); err != nil { return } case <-srvMngr.GetConfig().GetReloadChan(config.THRESHOLDS_JSON): if err = srvMngr.reloadService(utils.ThresholdS, srvMngr.GetConfig().ThresholdSCfg().Enabled); err != nil { return } case <-srvMngr.GetConfig().GetReloadChan(config.STATS_JSON): if err = srvMngr.reloadService(utils.StatS, srvMngr.GetConfig().StatSCfg().Enabled); err != nil { return } case <-srvMngr.GetConfig().GetReloadChan(config.RESOURCES_JSON): if err = srvMngr.reloadService(utils.ResourceS, srvMngr.GetConfig().ResourceSCfg().Enabled); err != nil { return } case <-srvMngr.GetConfig().GetReloadChan(config.SupplierSJson): if err = srvMngr.reloadService(utils.SupplierS, srvMngr.GetConfig().SupplierSCfg().Enabled); err != nil { return } case <-srvMngr.GetConfig().GetReloadChan(config.SCHEDULER_JSN): if err = srvMngr.reloadService(utils.SchedulerS, srvMngr.GetConfig().SchedulerCfg().Enabled); err != nil { return } case <-srvMngr.GetConfig().GetReloadChan(config.CDRS_JSN): if err = srvMngr.reloadService(utils.CDRServer, srvMngr.GetConfig().CdrsCfg().Enabled); err != nil { return } case <-srvMngr.GetConfig().GetReloadChan(config.RALS_JSN): if err = srvMngr.reloadService(utils.RALService, srvMngr.GetConfig().RalsCfg().RALsEnabled); err != nil { return } } // handle RPC server } } func (srvMngr *ServiceManager) reloadService(srviceName string, shouldRun bool) (err error) { srv, has := srvMngr.GetService(srviceName) if !has { // this should not happen (check the added services) utils.Logger.Err(fmt.Sprintf("<%s> Failed to find needed subsystem <%s>", utils.ServiceManager, srviceName)) srvMngr.engineShutdown <- true return } if shouldRun { if srv.IsRunning() { if err = srv.Reload(srvMngr); err != nil { utils.Logger.Err(fmt.Sprintf("<%s> Failed to reload <%s>", utils.ServiceManager, srv.ServiceName())) srvMngr.engineShutdown <- true return // stop if we encounter an error } } else { if err = srv.Start(srvMngr, true); err != nil { utils.Logger.Err(fmt.Sprintf("<%s> Failed to start <%s>", utils.ServiceManager, srv.ServiceName())) srvMngr.engineShutdown <- true return // stop if we encounter an error } } } else if srv.IsRunning() { if err = srv.Shutdown(); err != nil { utils.Logger.Err(fmt.Sprintf("<%s> Failed to stop service <%s>", utils.ServiceManager, srv.ServiceName())) srvMngr.engineShutdown <- true return // stop if we encounter an error } } return } func (srvMngr *ServiceManager) startService(srviceName string) { srv, has := srvMngr.GetService(srviceName) if !has { // this should not happen (check the added services) utils.Logger.Err(fmt.Sprintf("<%s> Failed to find needed subsystem <%s>", utils.ServiceManager, srviceName)) srvMngr.engineShutdown <- true return } if err := srv.Start(srvMngr, true); err != nil { utils.Logger.Err(fmt.Sprintf("<%s> Failed to start %s because: %s", utils.ServiceManager, srviceName, err)) srvMngr.engineShutdown <- true } } // GetService returns the named service func (srvMngr ServiceManager) GetService(subsystem string) (srv Service, has bool) { srvMngr.RLock() srv, has = srvMngr.subsystems[subsystem] srvMngr.RUnlock() return } // SetCacheS sets the cacheS func (srvMngr *ServiceManager) SetCacheS(chS *engine.CacheS) { srvMngr.Lock() srvMngr.cacheS = chS srvMngr.Unlock() } // ServiceProvider should implement this to provide information for service type ServiceProvider interface { // GetDM returns the DataManager GetDM() *engine.DataManager // GetCDRStorage returns the CdrStorage GetCDRStorage() engine.CdrStorage // GetLoadStorage returns the LoadStorage GetLoadStorage() engine.LoadStorage // GetConfig returns the Configuration GetConfig() *config.CGRConfig // GetCacheS returns the CacheS GetCacheS() *engine.CacheS // GetFilterS returns the FilterS GetFilterS() *engine.FilterS // GetServer returns the Server GetServer() *utils.Server // GetExitChan returns the exit chanel GetExitChan() chan bool // GetConnection creates a rpcClient to the specified subsystem GetConnection(subsystem string, cfg []*config.RemoteHost) (rpcclient.RpcClientConnection, error) // GetService returns the named service GetService(subsystem string) (Service, bool) // AddService adds the given serices AddService(services ...Service) // SetCacheS sets the cacheS // Called when starting Cache Service SetCacheS(chS *engine.CacheS) } // Service interface that describes what functions should a service implement type Service interface { // Start should handle the sercive start Start(sp ServiceProvider, waitCache bool) error // Reload handles the change of config Reload(sp ServiceProvider) error // GetRPCInterface returns the interface to register for server GetRPCInterface() interface{} // GetIntenternalChan returns the internal connection chanel GetIntenternalChan() chan rpcclient.RpcClientConnection // IsRunning returns if the service is running IsRunning() bool // ServiceName returns the service name ServiceName() string // Shutdown stops the service Shutdown() error }