diff --git a/config/config.go b/config/config.go index 6b5a1010e..d11e11173 100644 --- a/config/config.go +++ b/config/config.go @@ -45,6 +45,7 @@ var ( dfltFsConnConfig *FsConnConfig // Default FreeSWITCH Connection configuration, built out of json default configuration dfltKamConnConfig *KamConnConfig // Default Kamailio Connection configuration dfltHaPoolConfig *HaPoolConfig + dfltAstConnCfg *AsteriskConnCfg ) // Used to retrieve system configuration from other packages @@ -66,6 +67,7 @@ func NewDefaultCGRConfig() (*CGRConfig, error) { cfg.SmFsConfig = new(SmFsConfig) cfg.SmKamConfig = new(SmKamConfig) cfg.SmOsipsConfig = new(SmOsipsConfig) + cfg.SMAsteriskCfg = new(SMAsteriskCfg) cfg.diameterAgentCfg = new(DiameterAgentCfg) cfg.ConfigReloads = make(map[string]chan struct{}) cfg.ConfigReloads[utils.CDRC] = make(chan struct{}, 1) @@ -88,6 +90,7 @@ func NewDefaultCGRConfig() (*CGRConfig, error) { cfg.dfltCdrcProfile = cfg.CdrcProfiles["/var/spool/cgrates/cdrc/in"][0].Clone() dfltFsConnConfig = cfg.SmFsConfig.EventSocketConns[0] // We leave it crashing here on purpose if no Connection defaults defined dfltKamConnConfig = cfg.SmKamConfig.EvapiConns[0] + dfltAstConnCfg = cfg.SMAsteriskCfg.AsteriskConns[0] if err := cfg.checkConfigSanity(); err != nil { return nil, err } @@ -242,6 +245,7 @@ type CGRConfig struct { SmFsConfig *SmFsConfig // SMFreeSWITCH configuration SmKamConfig *SmKamConfig // SM-Kamailio Configuration SmOsipsConfig *SmOsipsConfig // SMOpenSIPS Configuration + SMAsteriskCfg *SMAsteriskCfg // SMAsterisk Configuration diameterAgentCfg *DiameterAgentCfg // DiameterAgent configuration HistoryServer string // Address where to reach the master history server: HistoryServerEnabled bool // Starts History as server: . @@ -421,7 +425,7 @@ func (self *CGRConfig) checkConfigSanity() error { } for _, smOsipsRaterConn := range self.SmOsipsConfig.RALsConns { if smOsipsRaterConn.Address == utils.MetaInternal && !self.RALsEnabled { - return errors.New(" RALs not enabled but requested by SMOpenSIPS component.") + return errors.New(" RALs not enabled.") } } if len(self.SmOsipsConfig.CDRsConns) == 0 { @@ -430,7 +434,18 @@ func (self *CGRConfig) checkConfigSanity() error { for _, smOsipsCDRSConn := range self.SmOsipsConfig.CDRsConns { if smOsipsCDRSConn.Address == utils.MetaInternal && !self.CDRSEnabled { - return errors.New(" CDRS not enabled but referenced by SMOpenSIPS component") + return errors.New(" CDRS not enabled.") + } + } + } + // SMOpenSIPS checks + if self.SMAsteriskCfg.Enabled { + if len(self.SMAsteriskCfg.SMGConns) == 0 { + return errors.New(" SMG definition is mandatory!") + } + for _, smAstSMGConn := range self.SMAsteriskCfg.SMGConns { + if smAstSMGConn.Address == utils.MetaInternal && !self.SmGenericConfig.Enabled { + return errors.New(" SMG not enabled.") } } } @@ -548,6 +563,11 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { return err } + jsnSMAstCfg, err := jsnCfg.SmAsteriskJsonCfg() + if err != nil { + return err + } + jsnDACfg, err := jsnCfg.DiameterAgentJsonCfg() if err != nil { return err @@ -975,6 +995,12 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { } } + if jsnSMAstCfg != nil { + if err := self.SMAsteriskCfg.loadFromJsonCfg(jsnSMAstCfg); err != nil { + return err + } + } + if jsnDACfg != nil { if err := self.diameterAgentCfg.loadFromJsonCfg(jsnDACfg); err != nil { return err diff --git a/config/config_defaults.go b/config/config_defaults.go index 79b15a930..d7b6469d5 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -331,6 +331,21 @@ const CGRATES_CFG_JSON = ` }, +"sm_asterisk": { + "enabled": false, // starts Asterisk SessionManager service: + "sm_generic_conns": [ + {"address": "*internal"} // connection towards SMG component for session management + ], + "session_terminate_subscriber": {"address": "*internal"}, // handler for session_terminate events generated by SMG + "debit_interval": "10s", // interval to perform debits on. + "min_call_duration": "0s", // only authorize calls with allowed duration higher than this + "max_call_duration": "3h", // maximum call duration a prepaid call can last + "asterisk_conns":[ // instantiate connections to multiple Asterisk servers + {"address": "127.0.0.1:8088", "user": "cgrates", "password": "CGRateS.org", "reconnects": 5} + ], +}, + + "diameter_agent": { "enabled": false, // enables the diameter agent: "listen": "127.0.0.1:3868", // address where to listen for diameter requests diff --git a/config/config_json.go b/config/config_json.go index 3b1358c04..317d03584 100644 --- a/config/config_json.go +++ b/config/config_json.go @@ -44,6 +44,7 @@ const ( SMFS_JSN = "sm_freeswitch" SMKAM_JSN = "sm_kamailio" SMOSIPS_JSN = "sm_opensips" + SMAsteriskJSN = "sm_asterisk" SM_JSN = "session_manager" FS_JSN = "freeswitch" KAMAILIO_JSN = "kamailio" @@ -261,6 +262,18 @@ func (self CgrJsonCfg) SmOsipsJsonCfg() (*SmOsipsJsonCfg, error) { return cfg, nil } +func (self CgrJsonCfg) SmAsteriskJsonCfg() (*SMAsteriskJsonCfg, error) { + rawCfg, hasKey := self[SMAsteriskJSN] + if !hasKey { + return nil, nil + } + cfg := new(SMAsteriskJsonCfg) + if err := json.Unmarshal(*rawCfg, cfg); err != nil { + return nil, err + } + return cfg, nil +} + func (self CgrJsonCfg) DiameterAgentJsonCfg() (*DiameterAgentJsonCfg, error) { rawCfg, hasKey := self[DA_JSN] if !hasKey { diff --git a/config/config_json_test.go b/config/config_json_test.go index f60f48bcc..b90464109 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -516,6 +516,33 @@ func TestSmOsipsJsonCfg(t *testing.T) { } } +func TestSmAsteriskJsonCfg(t *testing.T) { + eCfg := &SMAsteriskJsonCfg{ + Enabled: utils.BoolPointer(false), + Sm_generic_conns: &[]*HaPoolJsonCfg{ + &HaPoolJsonCfg{ + Address: utils.StringPointer(utils.MetaInternal), + }}, + Session_terminate_subscriber: &HaPoolJsonCfg{Address: utils.StringPointer(utils.MetaInternal)}, + Debit_interval: utils.StringPointer("10s"), + Min_call_duration: utils.StringPointer("0s"), + Max_call_duration: utils.StringPointer("3h"), + Asterisk_conns: &[]*AstConnJsonCfg{ + &AstConnJsonCfg{ + Address: utils.StringPointer("127.0.0.1:8088"), + User: utils.StringPointer("cgrates"), + Password: utils.StringPointer("CGRateS.org"), + Reconnects: utils.IntPointer(5), + }, + }, + } + if cfg, err := dfCgrJsonCfg.SmAsteriskJsonCfg(); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eCfg, cfg) { + t.Error("Received: ", cfg) + } +} + func TestDiameterAgentJsonCfg(t *testing.T) { eCfg := &DiameterAgentJsonCfg{ Enabled: utils.BoolPointer(false), diff --git a/config/libconfig_json.go b/config/libconfig_json.go index 28906f3d1..a0c743e85 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -214,6 +214,23 @@ type HaPoolJsonCfg struct { Transport *string } +type AstConnJsonCfg struct { + Address *string + User *string + Password *string + Reconnects *int +} + +type SMAsteriskJsonCfg struct { + Enabled *bool + Sm_generic_conns *[]*HaPoolJsonCfg // Connections towards generic SM + Session_terminate_subscriber *HaPoolJsonCfg + Debit_interval *string + Min_call_duration *string + Max_call_duration *string + Asterisk_conns *[]*AstConnJsonCfg +} + type CacheParamJsonCfg struct { Limit *int Ttl *string diff --git a/config/smconfig.go b/config/smconfig.go index c6c9ac6b2..b3738e92e 100644 --- a/config/smconfig.go +++ b/config/smconfig.go @@ -435,3 +435,85 @@ func (self *SmOsipsConfig) loadFromJsonCfg(jsnCfg *SmOsipsJsonCfg) error { return nil } + +// Uses stored defaults so we can pre-populate by loading from JSON config +func NewDefaultAsteriskConnCfg() *AsteriskConnCfg { + if dfltAstConnCfg == nil { + return new(AsteriskConnCfg) // No defaults, most probably we are building the defaults now + } + dfltVal := *dfltAstConnCfg // Copy the value instead of it's pointer + return &dfltVal +} + +type AsteriskConnCfg struct { + Address string + User string + Password string + Reconnects int +} + +func (aConnCfg *AsteriskConnCfg) loadFromJsonCfg(jsnCfg *AstConnJsonCfg) error { + if jsnCfg.Address != nil { + aConnCfg.Address = *jsnCfg.Address + } + if jsnCfg.User != nil { + aConnCfg.User = *jsnCfg.User + } + if jsnCfg.Password != nil { + aConnCfg.Password = *jsnCfg.Password + } + if jsnCfg.Reconnects != nil { + aConnCfg.Reconnects = *jsnCfg.Reconnects + } + return nil +} + +type SMAsteriskCfg struct { + Enabled bool + SMGConns []*HaPoolConfig + SessionTerminateSubscriber *HaPoolConfig + DebitInterval time.Duration + MinCallDuration time.Duration + MaxCallDuration time.Duration + AsteriskConns []*AsteriskConnCfg +} + +func (aCfg *SMAsteriskCfg) loadFromJsonCfg(jsnCfg *SMAsteriskJsonCfg) (err error) { + if jsnCfg.Enabled != nil { + aCfg.Enabled = *jsnCfg.Enabled + } + if jsnCfg.Sm_generic_conns != nil { + aCfg.SMGConns = make([]*HaPoolConfig, len(*jsnCfg.Sm_generic_conns)) + for idx, jsnHaCfg := range *jsnCfg.Sm_generic_conns { + aCfg.SMGConns[idx] = NewDfltHaPoolConfig() + aCfg.SMGConns[idx].loadFromJsonCfg(jsnHaCfg) + } + } + if jsnCfg.Session_terminate_subscriber != nil { + aCfg.SessionTerminateSubscriber = NewDfltHaPoolConfig() + aCfg.SessionTerminateSubscriber.loadFromJsonCfg(jsnCfg.Session_terminate_subscriber) + } + if jsnCfg.Debit_interval != nil { + if aCfg.DebitInterval, err = utils.ParseDurationWithSecs(*jsnCfg.Debit_interval); err != nil { + return err + } + } + if jsnCfg.Min_call_duration != nil { + if aCfg.MinCallDuration, err = utils.ParseDurationWithSecs(*jsnCfg.Min_call_duration); err != nil { + return err + } + } + if jsnCfg.Max_call_duration != nil { + if aCfg.MaxCallDuration, err = utils.ParseDurationWithSecs(*jsnCfg.Max_call_duration); err != nil { + return err + } + } + if jsnCfg.Asterisk_conns != nil { + aCfg.AsteriskConns = make([]*AsteriskConnCfg, len(*jsnCfg.Asterisk_conns)) + for i, jsnAConn := range *jsnCfg.Asterisk_conns { + aCfg.AsteriskConns[i] = NewDefaultAsteriskConnCfg() + aCfg.AsteriskConns[i].loadFromJsonCfg(jsnAConn) + } + } + return nil +} diff --git a/sessionmanager/smasterisk.go b/sessionmanager/smasterisk.go new file mode 100644 index 000000000..093401504 --- /dev/null +++ b/sessionmanager/smasterisk.go @@ -0,0 +1,28 @@ +/* +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 sessionmanager + +import ( + "github.com/cgrates/cgrates/config" + "github.com/cgrates/rpcclient" +) + +type SMAsterisk struct { + cgrCfg *config.CGRConfig // Separate from smCfg since there can be multiple + smg rpcclient.RpcClientConnection +}