Added V1ReloadConfig

This commit is contained in:
Trial97
2019-08-28 14:59:19 +03:00
committed by Dan Christian Bogos
parent e614ec6942
commit 81bd7e8726
10 changed files with 352 additions and 88 deletions

View File

@@ -36,6 +36,10 @@ func (cSv1 *ConfigSv1) GetJSONSection(section *config.StringWithArgDispatcher, r
return cSv1.cfg.V1GetConfigSection(section, reply)
}
func (cSv1 *ConfigSv1) ReloadConfig(args *config.ConfigReloadWithArgDispatcher, reply *string) (err error) {
return cSv1.cfg.V1ReloadConfig(args, reply)
}
// Call implements rpcclient.RpcClientConnection interface for internal RPC
func (cSv1 *ConfigSv1) Call(serviceMethod string,
args interface{}, reply interface{}) error {

View File

@@ -767,11 +767,14 @@ type DispatcherConfigSv1 struct {
dS *dispatchers.DispatcherService
}
// Ping used to detreminate if component is active
func (dS *DispatcherConfigSv1) GetJSONSection(args *config.StringWithArgDispatcher, reply *map[string]interface{}) (err error) {
return dS.dS.ConfigSv1GetJSONSection(args, reply)
}
func (dS *DispatcherConfigSv1) ReloadConfig(args *config.ConfigReloadWithArgDispatcher, reply *string) (err error) {
return dS.dS.ConfigSv1ReloadConfig(args, reply)
}
func NewDispatcherCoreSv1(dps *dispatchers.DispatcherService) *DispatcherCoreSv1 {
return &DispatcherCoreSv1{dS: dps}
}

View File

@@ -21,6 +21,7 @@ package v1
import (
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/dispatchers"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/sessions"
@@ -150,3 +151,13 @@ type RALsV1Interface interface {
GetRatingPlansCost(arg *utils.RatingPlanCostArg, reply *dispatchers.RatingPlanCost) error
Ping(ign *utils.CGREventWithArgDispatcher, reply *string) error
}
type ConfigSv1Interface interface {
GetJSONSection(section *config.StringWithArgDispatcher, reply *map[string]interface{}) (err error)
ReloadConfig(section *config.ConfigReloadWithArgDispatcher, reply *string) (err error)
}
type CoreSv1Interface interface {
Status(arg *utils.TenantWithArgDispatcher, reply *map[string]interface{}) error
Ping(ign *utils.CGREventWithArgDispatcher, reply *string) error
}

View File

@@ -90,6 +90,16 @@ func TestServiceManagerV1Interface(t *testing.T) {
}
func TestRALsV1Interface(t *testing.T) {
_ = ServiceManagerV1Interface(NewDispatcherSServiceManagerV1(nil))
_ = ServiceManagerV1Interface(NewServiceManagerV1(nil))
_ = RALsV1Interface(NewDispatcherRALsV1(nil))
_ = RALsV1Interface(NewRALsV1())
}
func TestConfigSv1Interface(t *testing.T) {
_ = ConfigSv1Interface(NewDispatcherConfigSv1(nil))
_ = ConfigSv1Interface(NewConfigSv1(nil))
}
func TestCoreSv1Interface(t *testing.T) {
_ = CoreSv1Interface(NewDispatcherCoreSv1(nil))
_ = CoreSv1Interface(NewCoreSv1(nil))
}

View File

@@ -232,20 +232,14 @@ func NewCGRConfigFromPath(path string) (*CGRConfig, error) {
return nil, err
}
cfg.ConfigPath = path
if isUrl(path) {
return loadConfigFromHttp(cfg, path) // prefix protocol
}
fi, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return nil, utils.ErrPathNotReachable(path)
}
if err := updateConfigFromPath(path, func(jsnCfg *CgrJsonCfg) error {
return cfg.loadFromJsonCfg(jsnCfg)
}); err != nil {
return nil, err
} else if !fi.IsDir() && path != utils.CONFIG_PATH { // If config dir defined, needs to exist, not checking for default
return nil, fmt.Errorf("Path: %s not a directory.", path)
}
if fi.IsDir() {
return loadConfigFromFolder(cfg, path)
if err = cfg.checkConfigSanity(); err != nil { // should we check only the updated sections?
return nil, err
}
return cfg, nil
}
@@ -257,64 +251,6 @@ func isHidden(fileName string) bool {
return strings.HasPrefix(fileName, ".")
}
func loadConfigFromFolder(cfg *CGRConfig, cfgDir string) (*CGRConfig, error) {
jsonFilesFound := false
err := filepath.Walk(cfgDir, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() || isHidden(info.Name()) { // also ignore hidden files and folders
return nil
}
cfgFiles, err := filepath.Glob(filepath.Join(path, "*.json"))
if err != nil {
return err
}
if cfgFiles == nil { // No need of processing further since there are no config files in the folder
return nil
}
if !jsonFilesFound {
jsonFilesFound = true
}
for _, jsonFilePath := range cfgFiles {
if cgrJsonCfg, err := NewCgrJsonCfgFromFile(jsonFilePath); err != nil {
utils.Logger.Err(fmt.Sprintf("<CGR-CFG> Error <%s> reading config from path: <%s>", err.Error(), jsonFilePath))
return err
} else if err := cfg.loadFromJsonCfg(cgrJsonCfg); err != nil {
utils.Logger.Err(fmt.Sprintf("<CGR-CFG> Error <%s> loading config from path: <%s>", err.Error(), jsonFilePath))
return err
}
}
return nil
})
if err != nil {
return nil, err
}
if !jsonFilesFound {
return nil, fmt.Errorf("No config file found on path %s", cfgDir)
}
if err := cfg.checkConfigSanity(); err != nil {
return nil, err
}
return cfg, nil
}
func loadConfigFromHttp(cfg *CGRConfig, urlPaths string) (*CGRConfig, error) {
for _, urlPath := range strings.Split(urlPaths, utils.INFIELD_SEP) {
if _, err := url.ParseRequestURI(urlPath); err != nil {
return nil, err
}
if cgrJsonCfg, err := NewCgrJsonCfgFromHttp(urlPath); err != nil {
utils.Logger.Err(fmt.Sprintf("<CGR-CFG> Error <%s> reading config from path: <%s>", err.Error(), urlPath))
return nil, err
} else if err := cfg.loadFromJsonCfg(cgrJsonCfg); err != nil {
utils.Logger.Err(fmt.Sprintf("<CGR-CFG> Error <%s> loading config from path: <%s>", err.Error(), urlPath))
return nil, err
}
}
if err := cfg.checkConfigSanity(); err != nil {
return nil, err
}
return cfg, nil
}
// Holds system configuration, defaults are overwritten with values from config file if found
type CGRConfig struct {
lks map[string]*sync.RWMutex
@@ -988,7 +924,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) (err error) {
jsnLoaderCgrCfg, err := jsnCfg.LoaderCfgJson()
if err != nil {
return nil
return err
}
if self.loaderCgrCfg.loadFromJsonCfg(jsnLoaderCgrCfg); err != nil {
return err
@@ -996,7 +932,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) (err error) {
jsnMigratorCgrCfg, err := jsnCfg.MigratorCfgJson()
if err != nil {
return nil
return err
}
if self.migratorCgrCfg.loadFromJsonCfg(jsnMigratorCgrCfg); err != nil {
return err
@@ -1004,7 +940,7 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) (err error) {
jsnTlsCgrCfg, err := jsnCfg.TlsCfgJson()
if err != nil {
return nil
return err
}
if err := self.tlsCfg.loadFromJsonCfg(jsnTlsCgrCfg); err != nil {
return err
@@ -1012,24 +948,17 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) (err error) {
jsnAnalyzerCgrCfg, err := jsnCfg.AnalyzerCfgJson()
if err != nil {
return nil
return err
}
if err := self.analyzerSCfg.loadFromJsonCfg(jsnAnalyzerCgrCfg); err != nil {
return err
}
jsnApierCfg, err := jsnCfg.ApierCfgJson()
if err != nil {
return nil
}
if err := self.apier.loadFromJsonCfg(jsnApierCfg); err != nil {
if err := self.loadApierCfg(jsnCfg); err != nil {
return err
}
jsnERsCfg, err := jsnCfg.ERsJsonCfg()
if err != nil {
return nil
}
if err := self.ersCfg.loadFromJsonCfg(jsnERsCfg, self.generalCfg.RSRSep, self.dfltEvRdr); err != nil {
if err := self.loadErsCfg(jsnCfg); err != nil {
return err
}
@@ -1101,6 +1030,22 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) (err error) {
return nil
}
func (cfg *CGRConfig) loadApierCfg(jsnCfg *CgrJsonCfg) (err error) {
var jsnApierCfg *ApierJsonCfg
if jsnApierCfg, err = jsnCfg.ApierCfgJson(); err != nil {
return
}
return cfg.apier.loadFromJsonCfg(jsnApierCfg)
}
func (cfg *CGRConfig) loadErsCfg(jsnCfg *CgrJsonCfg) (err error) {
var jsnERsCfg *ERsJsonCfg
if jsnERsCfg, err = jsnCfg.ERsJsonCfg(); err != nil {
return
}
return cfg.ersCfg.loadFromJsonCfg(jsnERsCfg, cfg.GeneralCfg().RSRSep, self.dfltEvRdr)
}
// Use locking to retrieve the configuration, possibility later for runtime reload
func (self *CGRConfig) SureTaxCfg() *SureTaxCfg {
cfgChan := <-self.ConfigReloads[utils.SURETAX] // Lock config for read or reloads
@@ -1258,6 +1203,7 @@ func (cSv1 *CGRConfig) Call(serviceMethod string,
return utils.APIerRPCCall(cSv1, serviceMethod, args, reply)
}
// ToDo: move this structure in utils as is used in other packages
type StringWithArgDispatcher struct {
*utils.ArgDispatcher
utils.TenantArg
@@ -1332,9 +1278,159 @@ func (cfg *CGRConfig) V1GetConfigSection(args *StringWithArgDispatcher, reply *m
jsonString = utils.ToJSON(cfg.CdrcProfiles)
case CDRE_JSN:
jsonString = utils.ToJSON(cfg.CdreProfiles)
case ERsJson:
jsonString = utils.ToJSON(cfg.ERsCfg())
default:
return errors.New("Invalid section")
}
json.Unmarshal([]byte(jsonString), reply)
return
}
type ConfigReloadWithArgDispatcher struct {
*utils.ArgDispatcher
utils.TenantArg
Section string
Path string
}
func (cfg *CGRConfig) RLockSections() {
for _, lk := range cfg.lks {
lk.RLock()
}
}
func (cfg *CGRConfig) RUnlockSections() {
for _, lk := range cfg.lks {
lk.RUnlock()
}
}
func (cfg *CGRConfig) LockSections() {
for _, lk := range cfg.lks {
lk.Lock()
}
}
func (cfg *CGRConfig) UnlockSections() {
for _, lk := range cfg.lks {
lk.Unlock()
}
}
func (cfg *CGRConfig) V1ReloadConfig(args *ConfigReloadWithArgDispatcher, reply *string) (err error) {
var reloadFunction func()
if reloadFunction, err = cfg.loadConfig(args.Path, args.Section); err != nil {
return err
}
// lock all sections
cfg.RLockSections()
err = cfg.checkConfigSanity()
cfg.RUnlockSections() // unlock before checking the error
if err != nil {
return err
}
reloadFunction()
*reply = utils.OK
return nil
}
func (cfg *CGRConfig) loadConfig(path, section string) (reload func(), err error) {
var parseFunction func(jsnCfg *CgrJsonCfg) error
switch section {
case utils.EmptyString:
cfg.LockSections()
defer cfg.UnlockSections()
parseFunction = func(jsnCfg *CgrJsonCfg) error {
return cfg.loadFromJsonCfg(jsnCfg)
}
reload = func() {}
case ERsJson:
cfg.lks[ERsJson].Lock()
defer cfg.lks[ERsJson].Unlock()
parseFunction = func(jsnCfg *CgrJsonCfg) error {
return cfg.loadErsCfg(jsnCfg)
}
reload = func() { cfg.rldChans[ERsJson] <- struct{}{} }
default:
return nil, fmt.Errorf("Invalid section: <%s>", section)
}
err = updateConfigFromPath(path, parseFunction)
return
}
// Reads all .json files out of a folder/subfolders and loads them up in lexical order
func updateConfigFromPath(path string, parseFunction func(jsnCfg *CgrJsonCfg) error) error {
if isUrl(path) {
return updateConfigFromHttp(path, parseFunction) // prefix protocol
}
fi, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return utils.ErrPathNotReachable(path)
}
return err
} else if !fi.IsDir() && path != utils.CONFIG_PATH { // If config dir defined, needs to exist, not checking for default
return fmt.Errorf("Path: %s not a directory.", path)
}
if fi.IsDir() {
return updateConfigFromFolder(path, parseFunction)
}
return nil
}
func updateConfigFromFolder(cfgDir string, parseFunction func(jsnCfg *CgrJsonCfg) error) error {
jsonFilesFound := false
err := filepath.Walk(cfgDir, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() || isHidden(info.Name()) { // also ignore hidden files and folders
return nil
}
cfgFiles, err := filepath.Glob(filepath.Join(path, "*.json"))
if err != nil {
return err
}
if cfgFiles == nil { // No need of processing further since there are no config files in the folder
return nil
}
if !jsonFilesFound {
jsonFilesFound = true
}
for _, jsonFilePath := range cfgFiles {
if cgrJsonCfg, err := NewCgrJsonCfgFromFile(jsonFilePath); err != nil {
utils.Logger.Err(fmt.Sprintf("<CGR-CFG> Error <%s> reading config from path: <%s>", err.Error(), jsonFilePath))
return err
} else if err := parseFunction(cgrJsonCfg); err != nil {
utils.Logger.Err(fmt.Sprintf("<CGR-CFG> Error <%s> loading config from path: <%s>", err.Error(), jsonFilePath))
return err
}
}
return nil
})
if err != nil {
return err
}
if !jsonFilesFound {
return fmt.Errorf("No config file found on path %s", cfgDir)
}
return nil
}
func updateConfigFromHttp(urlPaths string, parseFunction func(jsnCfg *CgrJsonCfg) error) error {
for _, urlPath := range strings.Split(urlPaths, utils.INFIELD_SEP) {
if _, err := url.ParseRequestURI(urlPath); err != nil {
return err
}
if cgrJsonCfg, err := NewCgrJsonCfgFromHttp(urlPath); err != nil {
utils.Logger.Err(fmt.Sprintf("<CGR-CFG> Error <%s> reading config from path: <%s>", err.Error(), urlPath))
return err
} else if err := parseFunction(cgrJsonCfg); err != nil {
utils.Logger.Err(fmt.Sprintf("<CGR-CFG> Error <%s> loading config from path: <%s>", err.Error(), urlPath))
return err
}
}
return nil
}

View File

@@ -72,3 +72,57 @@ func TestNewCGRConfigFromPath(t *testing.T) {
}
}
func TestCgrCfgV1ReloadConfigSection(t *testing.T) {
expected := map[string]interface{}{
"Enabled": true,
"Readers": []interface{}{
map[string]interface{}{
"ConcurrentReqs": 0.,
"Content_fields": nil,
"Continue": false,
"FieldSep": "",
"Filters": nil,
"Flags": nil,
"Header_fields": nil,
"ID": "file_reader1",
"ProcessedPath": "",
"RunDelay": -1.,
"SourceID": "",
"SourcePath": "",
"Tenant": nil,
"Timezone": "",
"Trailer_fields": nil,
"Type": "",
"XmlRootPath": "",
},
},
"SessionSConns": []interface{}{
map[string]interface{}{
"Address": "*internal",
"Synchronous": false,
"TLS": false,
"Transport": "",
},
},
}
cfg, _ := NewDefaultCGRConfig()
var reply string
var rcv map[string]interface{}
if err := cfg.V1ReloadConfig(&ConfigReloadWithArgDispatcher{
Path: "/usr/share/cgrates/conf/samples/ers",
Section: ERsJson,
}, &reply); err != nil {
t.Fatal(err)
} else if reply != utils.OK {
t.Errorf("Expected: %s ,received: %s", utils.OK, reply)
}
if err := cfg.V1GetConfigSection(&StringWithArgDispatcher{Section: ERsJson}, &rcv); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expected, rcv) {
t.Errorf("Expected: %+v, received: %+v", utils.ToJSON(expected), utils.ToJSON(rcv))
}
}

66
console/reload_config.go Normal file
View File

@@ -0,0 +1,66 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package console
import (
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
func init() {
c := &CmdRelaodConfigSection{
name: "reload_config",
rpcMethod: utils.ConfigSv1ReloadConfig,
rpcParams: &config.ConfigReloadWithArgDispatcher{},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdRelaodConfigSection struct {
name string
rpcMethod string
rpcParams *config.ConfigReloadWithArgDispatcher
*CommandExecuter
}
func (self *CmdRelaodConfigSection) Name() string {
return self.name
}
func (self *CmdRelaodConfigSection) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdRelaodConfigSection) RpcParams(reset bool) interface{} {
if reset || self.rpcParams == nil {
self.rpcParams = &config.ConfigReloadWithArgDispatcher{ArgDispatcher: new(utils.ArgDispatcher)}
}
return self.rpcParams
}
func (self *CmdRelaodConfigSection) PostprocessRpcParams() error {
return nil
}
func (self *CmdRelaodConfigSection) RpcResult() interface{} {
var s string
return &s
}

View File

@@ -17,6 +17,6 @@ cgrates.org,ATTR_API_SCHD_AUTH,*auth,*string:~APIKey:sched12345,,,APIMethods,*co
cgrates.org,ATTR_API_CDRS_AUTH,*auth,*string:~APIKey:cdrs12345,,,APIMethods,*constant,CDRsV1.Ping&CDRsV1.ProcessEvent&CDRsV1.GetCDRs&CDRsV1.CountCDRs&CDRsV1.ProcessCDR&CDRsV1.ProcessExternalCDR,false,20
cgrates.org,ATTR_API_DSP_AUTH,*auth,*string:~APIKey:dsp12345,,,APIMethods,*constant,DispatcherSv1.Ping&DispatcherSv1.GetProfileForEvent,false,20
cgrates.org,ATTR_API_PSE_AUTH,*auth,*string:~APIKey:pse12345,,,APIMethods,*constant,SessionSv1.Ping&SessionSv1.AuthorizeEvent&SessionSv1.AuthorizeEventWithDigest&SessionSv1.InitiateSession&SessionSv1.InitiateSessionWithDigest&SessionSv1.UpdateSession&SessionSv1.SyncSessions&SessionSv1.TerminateSession&SessionSv1.ProcessCDR&SessionSv1.ProcessMessage&SessionSv1.GetActiveSessions&SessionSv1.GetActiveSessionsCount&SessionSv1.ForceDisconnect&SessionSv1.GetPassiveSessions&SessionSv1.GetPassiveSessionsCount&SessionSv1.ReplicateSessions&SessionSv1.SetPassiveSession&AttributeSv1.ProcessEvent&Responder.Debit&ResourceSv1.AllocateResources&ChargerSv1.ProcessEvent&Responder.MaxDebit,false,20
cgrates.org,ATTR_API_CFG_AUTH,*auth,*string:~APIKey:cfg12345,,,APIMethods,*constant,ConfigSv1.GetJSONSection,false,20
cgrates.org,ATTR_API_CFG_AUTH,*auth,*string:~APIKey:cfg12345,,,APIMethods,*constant,ConfigSv1.GetJSONSection&ConfigSv1.ReloadConfig,false,20
cgrates.org,ATTR_API_APIER_AUTH,*auth,*string:~APIKey:apier12345,,,APIMethods,*constant,ApierV1.GetAttributeProfile&ApierV1.SetAttributeProfile,false,20
cgrates.org,ATTR_API_RALS_AUTH,*auth,*string:~APIKey:rals12345,,,APIMethods,*constant,RALsV1.Ping&RALsV1.GetRatingPlansCost,false,20
1 #Tenant ID Contexts FilterIDs ActivationInterval AttributeFilterIDs FieldName Type Value Blocker Weight
17 cgrates.org ATTR_API_CDRS_AUTH *auth *string:~APIKey:cdrs12345 APIMethods *constant CDRsV1.Ping&CDRsV1.ProcessEvent&CDRsV1.GetCDRs&CDRsV1.CountCDRs&CDRsV1.ProcessCDR&CDRsV1.ProcessExternalCDR false 20
18 cgrates.org ATTR_API_DSP_AUTH *auth *string:~APIKey:dsp12345 APIMethods *constant DispatcherSv1.Ping&DispatcherSv1.GetProfileForEvent false 20
19 cgrates.org ATTR_API_PSE_AUTH *auth *string:~APIKey:pse12345 APIMethods *constant SessionSv1.Ping&SessionSv1.AuthorizeEvent&SessionSv1.AuthorizeEventWithDigest&SessionSv1.InitiateSession&SessionSv1.InitiateSessionWithDigest&SessionSv1.UpdateSession&SessionSv1.SyncSessions&SessionSv1.TerminateSession&SessionSv1.ProcessCDR&SessionSv1.ProcessMessage&SessionSv1.GetActiveSessions&SessionSv1.GetActiveSessionsCount&SessionSv1.ForceDisconnect&SessionSv1.GetPassiveSessions&SessionSv1.GetPassiveSessionsCount&SessionSv1.ReplicateSessions&SessionSv1.SetPassiveSession&AttributeSv1.ProcessEvent&Responder.Debit&ResourceSv1.AllocateResources&ChargerSv1.ProcessEvent&Responder.MaxDebit false 20
20 cgrates.org ATTR_API_CFG_AUTH *auth *string:~APIKey:cfg12345 APIMethods *constant ConfigSv1.GetJSONSection ConfigSv1.GetJSONSection&ConfigSv1.ReloadConfig false 20
21 cgrates.org ATTR_API_APIER_AUTH *auth *string:~APIKey:apier12345 APIMethods *constant ApierV1.GetAttributeProfile&ApierV1.SetAttributeProfile false 20
22 cgrates.org ATTR_API_RALS_AUTH *auth *string:~APIKey:rals12345 APIMethods *constant RALsV1.Ping&RALsV1.GetRatingPlansCost false 20

View File

@@ -43,3 +43,22 @@ func (dS *DispatcherService) ConfigSv1GetJSONSection(args *config.StringWithArgD
return dS.Dispatch(&utils.CGREvent{Tenant: tnt},
utils.MetaConfig, routeID, utils.ConfigSv1GetJSONSection, args, reply)
}
func (dS *DispatcherService) ConfigSv1ReloadConfig(args *config.ConfigReloadWithArgDispatcher, reply *string) (err error) {
tnt := utils.FirstNonEmpty(args.TenantArg.Tenant, dS.cfg.GeneralCfg().DefaultTenant)
if dS.attrS != nil {
if args.ArgDispatcher == nil {
return utils.NewErrMandatoryIeMissing(utils.ArgDispatcherField)
}
if err = dS.authorize(utils.ConfigSv1ReloadConfig, tnt,
args.APIKey, utils.TimePointer(time.Now())); err != nil {
return
}
}
var routeID *string
if args.ArgDispatcher != nil {
routeID = args.ArgDispatcher.RouteID
}
return dS.Dispatch(&utils.CGREvent{Tenant: tnt},
utils.MetaConfig, routeID, utils.ConfigSv1ReloadConfig, args, reply)
}

View File

@@ -833,6 +833,7 @@ const (
const (
ConfigSv1 = "ConfigSv1"
ConfigSv1GetJSONSection = "ConfigSv1.GetJSONSection"
ConfigSv1ReloadConfig = "ConfigSv1.ReloadConfig"
)
const (