diff --git a/README.md b/README.md
index 215409f56..845d9cd8d 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,6 @@ PDF, Epub, Manpage http://readthedocs.org/projects/cgrates/downloads/
API reference [godoc](http://godoc.org/github.com/cgrates/cgrates/apier)
-Also check irc.freenode.net#cgrates and [Google group](https://groups.google.com/forum/#!forum/cgrates) for a more real-time support.
+Also check [irc.freenode.net #cgrates](irc://irc.freenode.net:6667/cgrates) ([Webchat](http://webchat.freenode.net?randomnick=1&channels=%23cgrates)) and [Google group](https://groups.google.com/forum/#!forum/cgrates) for a more real-time support.
[](https://github.com/igrigorik/ga-beacon)
diff --git a/apier/apier.go b/apier/apier.go
index 51fe0db8b..84d710377 100644
--- a/apier/apier.go
+++ b/apier/apier.go
@@ -55,6 +55,12 @@ func (self *ApierV1) GetDestination(dstId string, reply *engine.Destination) err
return nil
}
+type AttrSetDestination struct { //ToDo
+ Id string
+ Prefixes []string
+ Overwrite bool
+}
+
func (self *ApierV1) GetRatingPlan(rplnId string, reply *engine.RatingPlan) error {
if rpln, err := self.RatingDb.GetRatingPlan(rplnId, false); err != nil {
return errors.New(utils.ERR_NOT_FOUND)
@@ -77,20 +83,21 @@ func (self *ApierV1) GetAccount(attr *utils.AttrGetAccount, reply *engine.Accoun
}
type AttrAddBalance struct {
- Tenant string
- Account string
- BalanceType string
- Direction string
- Value float64
- ExpirationDate string
- RatingSubject string
- DestinationId string
- Weight float64
- Overwrite bool // When true it will reset if the balance is already there
+ Tenant string
+ Account string
+ BalanceType string
+ Direction string
+ Value float64
+ ExpiryTime string
+ RatingSubject string
+ DestinationId string
+ Weight float64
+ SharedGroup string
+ Overwrite bool // When true it will reset if the balance is already there
}
func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
- expTime, err := utils.ParseDate(attr.ExpirationDate)
+ expTime, err := utils.ParseDate(attr.ExpiryTime)
if err != nil {
*reply = err.Error()
return err
@@ -131,6 +138,7 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
RatingSubject: attr.RatingSubject,
DestinationId: attr.DestinationId,
Weight: attr.Weight,
+ SharedGroup: attr.SharedGroup,
},
},
})
@@ -419,17 +427,19 @@ func (self *ApierV1) SetActionPlan(attrs AttrSetActionPlan, reply *string) error
}
type AttrAddActionTrigger struct {
- Tenant string
- Account string
- Direction string
- BalanceType string
- ThresholdType string
- ThresholdValue float64
- DestinationId string
- BalanceWeight float64
- BalanceExpiryTime string
- Weight float64
- ActionsId string
+ Tenant string
+ Account string
+ Direction string
+ BalanceType string
+ ThresholdType string
+ ThresholdValue float64
+ DestinationId string
+ BalanceRatingSubject string //ToDo
+ BalanceWeight float64
+ BalanceExpiryTime string
+ BalanceSharedGroup string //ToDo
+ Weight float64
+ ActionsId string
}
func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string) error {
diff --git a/apier/apier_local_test.go b/apier/apier_local_test.go
index d43291b81..a469f9ef7 100644
--- a/apier/apier_local_test.go
+++ b/apier/apier_local_test.go
@@ -1442,6 +1442,30 @@ func TestLocalGetCdrs(t *testing.T) {
}
}
+func TestLocalProcessCdr(t *testing.T) {
+ if !*testLocal {
+ return
+ }
+ var reply string
+ cdr := utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf",
+ CdrHost: "192.168.1.1", CdrSource: "test", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
+ SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID,
+ Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans",
+ }
+ if err := rater.Call("CDRSV1.ProcessCdr", cdr, &reply); err != nil {
+ t.Error("Unexpected error: ", err.Error())
+ } else if reply != utils.OK {
+ t.Error("Unexpected reply received: ", reply)
+ }
+ var cdrs []*utils.StoredCdr
+ req := utils.AttrGetCdrs{}
+ if err := rater.Call("ApierV1.GetCdrs", req, &cdrs); err != nil {
+ t.Error("Unexpected error: ", err.Error())
+ } else if len(cdrs) != 3 {
+ t.Error("Unexpected number of CDRs returned: ", len(cdrs))
+ }
+}
+
func TestLocalSetDC(t *testing.T) {
if !*testLocal {
return
diff --git a/apier/cdre.go b/apier/cdre.go
index f1f63933d..f98e655df 100644
--- a/apier/cdre.go
+++ b/apier/cdre.go
@@ -121,8 +121,9 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
if attr.MaskLength != nil {
maskLen = *attr.MaskLength
}
- cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunId, attr.TOR, attr.CdrHost, attr.CdrSource, attr.ReqType, attr.Direction,
- attr.Tenant, attr.Category, attr.Account, attr.Subject, attr.DestinationPrefix, attr.OrderIdStart, attr.OrderIdEnd, tStart, tEnd, attr.SkipErrors, attr.SkipRated, false)
+ cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunIds, attr.TORs, attr.CdrHosts, attr.CdrSources, attr.ReqTypes, attr.Directions,
+ attr.Tenants, attr.Categories, attr.Accounts, attr.Subjects, attr.DestinationPrefixes, attr.RatedAccounts, attr.RatedSubjects, attr.OrderIdStart, attr.OrderIdEnd,
+ tStart, tEnd, attr.SkipErrors, attr.SkipRated, false)
if err != nil {
return err
} else if len(cdrs) == 0 {
@@ -141,8 +142,11 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
if err := cdrexp.WriteToFile(filePath); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
- *reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), TotalCost: cdrexp.TotalCost(),
- ExportedCgrIds: cdrexp.PositiveExports(), UnexportedCgrIds: cdrexp.NegativeExports(), FirstOrderId: cdrexp.FirstOrderId(), LastOrderId: cdrexp.LastOrderId()}
+ *reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), TotalCost: cdrexp.TotalCost(), FirstOrderId: cdrexp.FirstOrderId(), LastOrderId: cdrexp.LastOrderId()}
+ if !attr.SuppressCgrIds {
+ reply.ExportedCgrIds = cdrexp.PositiveExports()
+ reply.UnexportedCgrIds = cdrexp.NegativeExports()
+ }
return nil
}
diff --git a/apier/cdrs.go b/apier/cdrs.go
index 4676869b2..7aadaab9d 100644
--- a/apier/cdrs.go
+++ b/apier/cdrs.go
@@ -59,8 +59,8 @@ func (apier *ApierV1) GetCdrs(attrs utils.AttrGetCdrs, reply *[]*utils.CgrCdrOut
return err
}
}
- if cdrs, err := apier.CdrDb.GetStoredCdrs(attrs.CgrIds, attrs.MediationRunId, attrs.TOR, attrs.CdrHost, attrs.CdrSource, attrs.ReqType, attrs.Direction,
- attrs.Tenant, attrs.Category, attrs.Account, attrs.Subject, attrs.DestinationPrefix,
+ if cdrs, err := apier.CdrDb.GetStoredCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions,
+ attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects,
attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.SkipErrors, attrs.SkipRated, false); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
diff --git a/mediator/mediator_test.go b/apier/cdrsv1.go
similarity index 55%
rename from mediator/mediator_test.go
rename to apier/cdrsv1.go
index ed1406934..3b638ddfd 100644
--- a/mediator/mediator_test.go
+++ b/apier/cdrsv1.go
@@ -1,6 +1,6 @@
/*
Real-time Charging System for Telecom & ISP environments
-Copyright (C) 2012-2014 ITsysCOM GmbH
+Copyright (C) ITsysCOM GmbH
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,4 +16,26 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-package mediator
+package apier
+
+import (
+ "fmt"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+)
+
+// Receive CDRs via RPC methods
+type CDRSV1 struct {
+ CdrSrv *engine.CDRS
+}
+
+func (cdrsrv *CDRSV1) ProcessCdr(cdr *utils.StoredCdr, reply *string) error {
+ if cdrsrv.CdrSrv == nil {
+ return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "CDRS_NOT_RUNNING")
+ }
+ if err := cdrsrv.CdrSrv.ProcessCdr(cdr); err != nil {
+ return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
+ }
+ *reply = utils.OK
+ return nil
+}
diff --git a/apier/mediator.go b/apier/mediator.go
index 6f44e388f..e5cc8c0a7 100644
--- a/apier/mediator.go
+++ b/apier/mediator.go
@@ -22,12 +22,12 @@ import (
"fmt"
"time"
- "github.com/cgrates/cgrates/mediator"
+ "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
type MediatorV1 struct {
- Medi *mediator.Mediator
+ Medi *engine.Mediator
}
// Remotely start mediation with specific runid, runs asynchronously, it's status will be displayed in syslog
@@ -47,7 +47,11 @@ func (self *MediatorV1) RateCdrs(attrs utils.AttrRateCdrs, reply *string) error
return err
}
}
- if err := self.Medi.RateCdrs(tStart, tEnd, attrs.RerateErrors, attrs.RerateRated, attrs.SendToStats); err != nil {
+ //RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string,
+ //orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated bool)
+ if err := self.Medi.RateCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions,
+ attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects,
+ attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.RerateErrors, attrs.RerateRated, attrs.SendToStats); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.OK
diff --git a/cdrc/cdrc.go b/cdrc/cdrc.go
index 5399f68e5..209ca1360 100644
--- a/cdrc/cdrc.go
+++ b/cdrc/cdrc.go
@@ -32,7 +32,6 @@ import (
"time"
"unicode/utf8"
- "github.com/cgrates/cgrates/cdrs"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/howeyc/fsnotify"
@@ -43,7 +42,7 @@ const (
FS_CSV = "freeswitch_csv"
)
-func NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string]*utils.RSRField, cdrServer *cdrs.CDRS) (*Cdrc, error) {
+func NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField, cdrServer *engine.CDRS) (*Cdrc, error) {
if len(csvSep) != 1 {
return nil, fmt.Errorf("Unsupported csv separator: %s", csvSep)
}
@@ -68,8 +67,8 @@ type Cdrc struct {
cdrSourceId string
runDelay time.Duration
csvSep rune
- cdrFields map[string]*utils.RSRField
- cdrServer *cdrs.CDRS // Reference towards internal cdrServer if that is the case
+ cdrFields map[string][]*utils.RSRField
+ cdrServer *engine.CDRS // Reference towards internal cdrServer if that is the case
httpClient *http.Client
}
@@ -89,16 +88,18 @@ func (self *Cdrc) Run() error {
func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) {
storedCdr := &utils.StoredCdr{CdrHost: "0.0.0.0", CdrSource: self.cdrSourceId, ExtraFields: make(map[string]string), Cost: -1}
var err error
- for cfgFieldName, cfgFieldRSR := range self.cdrFields {
+ for cfgFieldName, cfgFieldRSRs := range self.cdrFields {
var fieldVal string
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cdrType) {
- if strings.HasPrefix(cfgFieldRSR.Id, utils.STATIC_VALUE_PREFIX) {
- fieldVal = cfgFieldRSR.ParseValue("PLACEHOLDER")
- } else { // Dynamic value extracted using index
- if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx {
- return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName)
- } else {
- fieldVal = cfgFieldRSR.ParseValue(record[cfgFieldIdx])
+ for _, cfgFieldRSR := range cfgFieldRSRs {
+ if strings.HasPrefix(cfgFieldRSR.Id, utils.STATIC_VALUE_PREFIX) {
+ fieldVal += cfgFieldRSR.ParseValue("PLACEHOLDER")
+ } else { // Dynamic value extracted using index
+ if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx {
+ return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName)
+ } else {
+ fieldVal += cfgFieldRSR.ParseValue(record[cfgFieldIdx])
+ }
}
}
} else { // Modify here when we add more supported cdr formats
@@ -218,7 +219,7 @@ func (self *Cdrc) processFile(filePath string) error {
continue
}
if self.cdrsAddress == utils.INTERNAL {
- if err := self.cdrServer.ProcessRawCdr(storedCdr); err != nil {
+ if err := self.cdrServer.ProcessCdr(storedCdr); err != nil {
engine.Logger.Err(fmt.Sprintf(" Failed posting CDR, row: %d, error: %s", procRowNr, err.Error()))
continue
}
diff --git a/cdrc/cdrc_test.go b/cdrc/cdrc_test.go
index 7874c1e59..37b923477 100644
--- a/cdrc/cdrc_test.go
+++ b/cdrc/cdrc_test.go
@@ -22,8 +22,8 @@ import (
//"bytes"
//"encoding/csv"
//"fmt"
- "github.com/cgrates/cgrates/cdrs"
"github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
//"io"
"reflect"
@@ -34,10 +34,10 @@ import (
func TestRecordForkCdr(t *testing.T) {
cgrConfig, _ := config.NewDefaultCGRConfig()
- cgrConfig.CdrcCdrFields["supplier"] = &utils.RSRField{Id: "14"}
+ cgrConfig.CdrcCdrFields["supplier"] = []*utils.RSRField{&utils.RSRField{Id: "14"}}
csvSepRune, _ := utf8.DecodeRune([]byte(cgrConfig.CdrcCsvSep))
cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrType, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, csvSepRune,
- cgrConfig.CdrcCdrFields, new(cdrs.CDRS), nil}
+ cgrConfig.CdrcCdrFields, new(engine.CDRS), nil}
cdrRow := []string{"firstField", "secondField"}
_, err := cdrc.recordToStoredCdr(cdrRow)
if err == nil {
diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go
index da8530dab..d2a0be847 100644
--- a/cmd/cgr-engine/cgr-engine.go
+++ b/cmd/cgr-engine/cgr-engine.go
@@ -31,11 +31,9 @@ import (
"github.com/cgrates/cgrates/apier"
"github.com/cgrates/cgrates/balancer2go"
"github.com/cgrates/cgrates/cdrc"
- "github.com/cgrates/cgrates/cdrs"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/history"
- "github.com/cgrates/cgrates/mediator"
"github.com/cgrates/cgrates/scheduler"
"github.com/cgrates/cgrates/sessionmanager"
"github.com/cgrates/cgrates/utils"
@@ -51,6 +49,7 @@ const (
REDIS = "redis"
SAME = "same"
FS = "freeswitch"
+ OSIPS = "opensips"
)
var (
@@ -66,10 +65,10 @@ var (
exitChan = make(chan bool)
server = &engine.Server{}
scribeServer history.Scribe
- cdrServer *cdrs.CDRS
+ cdrServer *engine.CDRS
cdrStats *engine.Stats
sm sessionmanager.SessionManager
- medi *mediator.Mediator
+ medi *engine.Mediator
cfg *config.CGRConfig
err error
)
@@ -97,8 +96,8 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD
var client *rpcclient.RpcClient
var err error
- for i := 0; i < cfg.MediatorRaterReconnects; i++ {
- client, err = rpcclient.NewRpcClient("tcp", cfg.MediatorRater, 0, cfg.MediatorRaterReconnects, utils.GOB)
+ for i := 0; i < cfg.MediatorReconnects; i++ {
+ client, err = rpcclient.NewRpcClient("tcp", cfg.MediatorRater, 0, cfg.MediatorReconnects, utils.GOB)
if err == nil { //Connected so no need to reiterate
break
}
@@ -112,7 +111,7 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD
connector = &engine.RPCClientConnector{Client: client}
}
var err error
- medi, err = mediator.NewMediator(connector, loggerDb, cdrDb, cdrStats, cfg)
+ medi, err = engine.NewMediator(connector, loggerDb, cdrDb, cdrStats, cfg)
if err != nil {
engine.Logger.Crit(fmt.Sprintf("Mediator config parsing error: %v", err))
exitChan <- true
@@ -125,7 +124,7 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD
}
// Fires up a cdrc instance
-func startCdrc(cdrsChan chan struct{}, cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string]*utils.RSRField) {
+func startCdrc(cdrsChan chan struct{}, cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField) {
if cdrsAddress == utils.INTERNAL {
<-cdrsChan // Wait for CDRServer to come up before start processing
}
@@ -150,8 +149,8 @@ func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage
var client *rpcclient.RpcClient
var err error
- for i := 0; i < cfg.SMRaterReconnects; i++ {
- client, err = rpcclient.NewRpcClient("tcp", cfg.SMRater, 0, cfg.SMRaterReconnects, utils.GOB)
+ for i := 0; i < cfg.SMReconnects; i++ {
+ client, err = rpcclient.NewRpcClient("tcp", cfg.SMRater, 0, cfg.SMReconnects, utils.GOB)
if err == nil { //Connected so no need to reiterate
break
}
@@ -166,14 +165,15 @@ func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage
switch cfg.SMSwitchType {
case FS:
dp, _ := time.ParseDuration(fmt.Sprintf("%vs", cfg.SMDebitInterval))
- sm = sessionmanager.NewFSSessionManager(loggerDb, connector, dp)
- errConn := sm.Connect(cfg)
- if errConn != nil {
- engine.Logger.Err(fmt.Sprintf(" error: %s!", errConn))
- }
+ sm = sessionmanager.NewFSSessionManager(cfg, loggerDb, connector, dp)
+ case OSIPS:
+ sm, _ = sessionmanager.NewOSipsSessionManager(cfg, connector)
default:
engine.Logger.Err(fmt.Sprintf(" Unsupported session manger type: %s!", cfg.SMSwitchType))
}
+ if err = sm.Connect(); err != nil {
+ engine.Logger.Err(fmt.Sprintf(" error: %s!", err))
+ }
exitChan <- true
}
@@ -186,8 +186,11 @@ func startCDRS(responder *engine.Responder, cdrDb engine.CdrStorage, mediChan, d
return
}
}
- cdrServer = cdrs.New(cdrDb, medi, cdrStats, cfg)
+ cdrServer = engine.NewCdrS(cdrDb, medi, cdrStats, cfg)
cdrServer.RegisterHanlersToServer(server)
+ engine.Logger.Info("Registering CDRS RPC service.")
+ server.RpcRegister(&apier.CDRSV1{CdrSrv: cdrServer})
+ responder.CdrSrv = cdrServer // Make the cdrserver available for internal communication
close(doneChan)
}
diff --git a/config/config.go b/config/config.go
index 7e0cf853e..0a7399871 100644
--- a/config/config.go
+++ b/config/config.go
@@ -55,71 +55,80 @@ func SetCgrConfig(cfg *CGRConfig) {
// Holds system configuration, defaults are overwritten with values from config file if found
type CGRConfig struct {
- RatingDBType string
- RatingDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
- RatingDBPort string // The port to bind to.
- RatingDBName string // The name of the database to connect to.
- RatingDBUser string // The user to sign in as.
- RatingDBPass string // The user's password.
- AccountDBType string
- AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
- AccountDBPort string // The port to bind to.
- AccountDBName string // The name of the database to connect to.
- AccountDBUser string // The user to sign in as.
- AccountDBPass string // The user's password.
- StorDBType string // Should reflect the database type used to store logs
- StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
- StorDBPort string // Th e port to bind to.
- StorDBName string // The name of the database to connect to.
- StorDBUser string // The user to sign in as.
- StorDBPass string // The user's password.
- DBDataEncoding string // The encoding used to store object data in strings:
- RPCJSONListen string // RPC JSON listening address
- RPCGOBListen string // RPC GOB listening address
- HTTPListen string // HTTP listening address
- DefaultReqType string // Use this request type if not defined on top
- DefaultCategory string // set default type of record
- DefaultTenant string // set default tenant
- DefaultSubject string // set default rating subject, useful in case of fallback
- RoundingDecimals int // Number of decimals to round end prices at
- HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate
- XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document
- RaterEnabled bool // start standalone server (no balancer)
- RaterBalancer string // balancer address host:port
- BalancerEnabled bool
- SchedulerEnabled bool
- CDRSEnabled bool // Enable CDR Server service
- CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs
- CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
+ RatingDBType string
+ RatingDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
+ RatingDBPort string // The port to bind to.
+ RatingDBName string // The name of the database to connect to.
+ RatingDBUser string // The user to sign in as.
+ RatingDBPass string // The user's password.
+ AccountDBType string
+ AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
+ AccountDBPort string // The port to bind to.
+ AccountDBName string // The name of the database to connect to.
+ AccountDBUser string // The user to sign in as.
+ AccountDBPass string // The user's password.
+ StorDBType string // Should reflect the database type used to store logs
+ StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
+ StorDBPort string // Th e port to bind to.
+ StorDBName string // The name of the database to connect to.
+ StorDBUser string // The user to sign in as.
+ StorDBPass string // The user's password.
+ DBDataEncoding string // The encoding used to store object data in strings:
+ RPCJSONListen string // RPC JSON listening address
+ RPCGOBListen string // RPC GOB listening address
+ HTTPListen string // HTTP listening address
+ DefaultReqType string // Use this request type if not defined on top
+ DefaultCategory string // set default type of record
+ DefaultTenant string // set default tenant
+ DefaultSubject string // set default rating subject, useful in case of fallback
+ RoundingDecimals int // Number of decimals to round end prices at
+ HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate
+ XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document
+ RaterEnabled bool // start standalone server (no balancer)
+ RaterBalancer string // balancer address host:port
+ BalancerEnabled bool
+ SchedulerEnabled bool
+ CDRSEnabled bool // Enable CDR Server service
+ CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs
+ CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
CDRSStats string // Address where to reach the Mediator. <""|intenal>
CDRStatsEnabled bool // Enable CDR Stats service
- //CdrStats []*cdrstats.CdrStats // Active cdr stats configuration instances
- CdreDefaultInstance *CdreConfig // Will be used in the case no specific one selected by API
- CdrcEnabled bool // Enable CDR client functionality
- CdrcCdrs string // Address where to reach CDR server
- CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
- CdrcCdrType string // CDR file format .
- CdrcCsvSep string // Separator used in case of csv files. One character only supported.
- CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored.
- CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved.
- CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database.
- CdrcCdrFields map[string]*utils.RSRField // FieldName/RSRField format. Index number in case of .csv cdrs.
+ //CdrStats []*cdrstats.CdrStats // Active cdr stats configuration instances
+ CdreDefaultInstance *CdreConfig // Will be used in the case no specific one selected by API
+ CdrcEnabled bool // Enable CDR client functionality
+ CdrcCdrs string // Address where to reach CDR server
+ CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
+ CdrcCdrType string // CDR file format .
+ CdrcCsvSep string // Separator used in case of csv files. One character only supported.
+ CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored.
+ CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved.
+ CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database.
+ CdrcCdrFields map[string][]*utils.RSRField // FieldName/RSRField format. Index number in case of .csv cdrs.
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
- SMRaterReconnects int // Number of reconnect attempts to rater
+ SMReconnects int // Number of reconnect attempts to rater
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
SMMaxCallDuration time.Duration // The maximum duration of a call
SMMinCallDuration time.Duration // Only authorize calls with allowed duration bigger than this
MediatorEnabled bool // Starts Mediator service: .
- MediatorRater string // Address where to reach the Rater:
- MediatorStats string // Address where to reach the stats service:
- MediatorRaterReconnects int // Number of reconnects to rater before giving up.
+ MediatorRater string
+ MediatorStats string // Address where to reach the Rater:
+ MediatorReconnects int // Number of reconnects to rater before giving up.
DerivedChargers utils.DerivedChargers // System wide derived chargers, added to the account level ones
CombinedDerivedChargers bool // Combine accounts specific derived_chargers with server configured
FreeswitchServer string // freeswitch address host:port
FreeswitchPass string // FS socket password
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
+ FSMinDurLowBalance time.Duration // Threshold which will trigger low balance warnings
+ FSLowBalanceAnnFile string // File to be played when low balance is reached
+ FSEmptyBalanceContext string // If defined, call will be transfered to this context on empty balance
+ FSEmptyBalanceAnnFile string // File to be played before disconnecting prepaid calls (applies only if no context defined)
+ OsipsListenUdp string // Address where to listen for event datagrams coming from OpenSIPS
+ OsipsMiAddr string // Adress where to reach OpenSIPS mi_datagram module
+ OsipsEvSubscInterval time.Duration // Refresh event subscription at this interval
+ OsipCDRS string // Address where to reach CDR Server, empty to disable CDR processing <""|internal|127.0.0.1:2013>
+ OsipsReconnects int // Number of attempts on connect failure.
HistoryAgentEnabled bool // Starts History as an agent: .
HistoryServer string // Address where to reach the master history server:
HistoryServerEnabled bool // Starts History as server: .
@@ -179,36 +188,45 @@ func (self *CGRConfig) setDefaults() error {
self.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
self.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
self.CdrcSourceId = "csv"
- self.CdrcCdrFields = map[string]*utils.RSRField{
- utils.TOR: &utils.RSRField{Id: "2"},
- utils.ACCID: &utils.RSRField{Id: "3"},
- utils.REQTYPE: &utils.RSRField{Id: "4"},
- utils.DIRECTION: &utils.RSRField{Id: "5"},
- utils.TENANT: &utils.RSRField{Id: "6"},
- utils.CATEGORY: &utils.RSRField{Id: "7"},
- utils.ACCOUNT: &utils.RSRField{Id: "8"},
- utils.SUBJECT: &utils.RSRField{Id: "9"},
- utils.DESTINATION: &utils.RSRField{Id: "10"},
- utils.SETUP_TIME: &utils.RSRField{Id: "11"},
- utils.ANSWER_TIME: &utils.RSRField{Id: "12"},
- utils.USAGE: &utils.RSRField{Id: "13"},
+ self.CdrcCdrFields = map[string][]*utils.RSRField{
+ utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}},
+ utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}},
+ utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}},
+ utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}},
+ utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
+ utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}},
+ utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}},
+ utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}},
+ utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}},
+ utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}},
+ utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}},
+ utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}},
}
self.MediatorEnabled = false
self.MediatorRater = utils.INTERNAL
- self.MediatorRaterReconnects = 3
+ self.MediatorReconnects = 3
self.MediatorStats = utils.INTERNAL
self.DerivedChargers = make(utils.DerivedChargers, 0)
self.CombinedDerivedChargers = true
self.SMEnabled = false
self.SMSwitchType = FS
self.SMRater = utils.INTERNAL
- self.SMRaterReconnects = 3
+ self.SMReconnects = 3
self.SMDebitInterval = 10
self.SMMaxCallDuration = time.Duration(3) * time.Hour
self.SMMinCallDuration = time.Duration(0)
self.FreeswitchServer = "127.0.0.1:8021"
self.FreeswitchPass = "ClueCon"
self.FreeswitchReconnects = 5
+ self.FSMinDurLowBalance = time.Duration(5) * time.Second
+ self.FSLowBalanceAnnFile = ""
+ self.FSEmptyBalanceContext = ""
+ self.FSEmptyBalanceAnnFile = ""
+ self.OsipsListenUdp = "127.0.0.1:2020"
+ self.OsipsMiAddr = "127.0.0.1:8020"
+ self.OsipsEvSubscInterval = time.Duration(60) * time.Second
+ self.OsipCDRS = "internal"
+ self.OsipsReconnects = 3
self.HistoryAgentEnabled = false
self.HistoryServerEnabled = false
self.HistoryServer = utils.INTERNAL
@@ -227,9 +245,11 @@ func (self *CGRConfig) checkConfigSanity() error {
return errors.New("CdrC enabled but no fields to be processed defined!")
}
if self.CdrcCdrType == utils.CSV {
- for _, rsrFld := range self.CdrcCdrFields {
- if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil {
- return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id)
+ for _, rsrFldLst := range self.CdrcCdrFields {
+ for _, rsrFld := range rsrFldLst {
+ if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil {
+ return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id)
+ }
}
}
}
@@ -503,8 +523,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("mediator", "rater"); hasOpt {
cfg.MediatorRater, _ = c.GetString("mediator", "rater")
}
- if hasOpt = c.HasOption("mediator", "rater_reconnects"); hasOpt {
- cfg.MediatorRaterReconnects, _ = c.GetInt("mediator", "rater_reconnects")
+ if hasOpt = c.HasOption("mediator", "reconnects"); hasOpt {
+ cfg.MediatorReconnects, _ = c.GetInt("mediator", "reconnects")
}
if hasOpt = c.HasOption("mediator", "stats"); hasOpt {
cfg.MediatorStats, _ = c.GetString("mediator", "stats")
@@ -518,8 +538,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("session_manager", "rater"); hasOpt {
cfg.SMRater, _ = c.GetString("session_manager", "rater")
}
- if hasOpt = c.HasOption("session_manager", "rater_reconnects"); hasOpt {
- cfg.SMRaterReconnects, _ = c.GetInt("session_manager", "rater_reconnects")
+ if hasOpt = c.HasOption("session_manager", "reconnects"); hasOpt {
+ cfg.SMReconnects, _ = c.GetInt("session_manager", "reconnects")
}
if hasOpt = c.HasOption("session_manager", "debit_interval"); hasOpt {
cfg.SMDebitInterval, _ = c.GetInt("session_manager", "debit_interval")
@@ -531,7 +551,7 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
}
}
if hasOpt = c.HasOption("session_manager", "max_call_duration"); hasOpt {
- maxCallDurStr, _ := c.GetString("session_manager", "min_call_duration")
+ maxCallDurStr, _ := c.GetString("session_manager", "max_call_duration")
if cfg.SMMaxCallDuration, err = utils.ParseDurationWithSecs(maxCallDurStr); err != nil {
return nil, err
}
@@ -545,6 +565,39 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("freeswitch", "reconnects"); hasOpt {
cfg.FreeswitchReconnects, _ = c.GetInt("freeswitch", "reconnects")
}
+ if hasOpt = c.HasOption("freeswitch", "min_dur_low_balance"); hasOpt {
+ minDurStr, _ := c.GetString("freeswitch", "min_dur_low_balance")
+ if cfg.FSMinDurLowBalance, err = utils.ParseDurationWithSecs(minDurStr); err != nil {
+ return nil, err
+ }
+ }
+ if hasOpt = c.HasOption("freeswitch", "low_balance_ann_file"); hasOpt {
+ cfg.FSLowBalanceAnnFile, _ = c.GetString("freeswitch", "low_balance_ann_file")
+ }
+ if hasOpt = c.HasOption("freeswitch", "empty_balance_context"); hasOpt {
+ cfg.FSEmptyBalanceContext, _ = c.GetString("freeswitch", "empty_balance_context")
+ }
+ if hasOpt = c.HasOption("freeswitch", "empty_balance_ann_file"); hasOpt {
+ cfg.FSEmptyBalanceAnnFile, _ = c.GetString("freeswitch", "empty_balance_ann_file")
+ }
+ if hasOpt = c.HasOption("opensips", "listen_udp"); hasOpt {
+ cfg.OsipsListenUdp, _ = c.GetString("opensips", "listen_udp")
+ }
+ if hasOpt = c.HasOption("opensips", "mi_addr"); hasOpt {
+ cfg.OsipsMiAddr, _ = c.GetString("opensips", "mi_addr")
+ }
+ if hasOpt = c.HasOption("opensips", "events_subscribe_interval"); hasOpt {
+ evSubscIntervalStr, _ := c.GetString("opensips", "events_subscribe_interval")
+ if cfg.OsipsEvSubscInterval, err = utils.ParseDurationWithSecs(evSubscIntervalStr); err != nil {
+ return nil, err
+ }
+ }
+ if hasOpt = c.HasOption("opensips", "cdrs"); hasOpt {
+ cfg.OsipCDRS, _ = c.GetString("opensips", "cdrs")
+ }
+ if hasOpt = c.HasOption("opensips", "reconnects"); hasOpt {
+ cfg.OsipsReconnects, _ = c.GetInt("opensips", "reconnects")
+ }
if cfg.DerivedChargers, err = ParseCfgDerivedCharging(c); err != nil {
return nil, err
}
diff --git a/config/config_test.go b/config/config_test.go
index 4150f6720..991d916e1 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -92,19 +92,19 @@ func TestDefaults(t *testing.T) {
eCfg.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
eCfg.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
eCfg.CdrcSourceId = "csv"
- eCfg.CdrcCdrFields = map[string]*utils.RSRField{
- utils.TOR: &utils.RSRField{Id: "2"},
- utils.ACCID: &utils.RSRField{Id: "3"},
- utils.REQTYPE: &utils.RSRField{Id: "4"},
- utils.DIRECTION: &utils.RSRField{Id: "5"},
- utils.TENANT: &utils.RSRField{Id: "6"},
- utils.CATEGORY: &utils.RSRField{Id: "7"},
- utils.ACCOUNT: &utils.RSRField{Id: "8"},
- utils.SUBJECT: &utils.RSRField{Id: "9"},
- utils.DESTINATION: &utils.RSRField{Id: "10"},
- utils.SETUP_TIME: &utils.RSRField{Id: "11"},
- utils.ANSWER_TIME: &utils.RSRField{Id: "12"},
- utils.USAGE: &utils.RSRField{Id: "13"},
+ eCfg.CdrcCdrFields = map[string][]*utils.RSRField{
+ utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}},
+ utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}},
+ utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}},
+ utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}},
+ utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
+ utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}},
+ utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}},
+ utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}},
+ utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}},
+ utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}},
+ utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}},
+ utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}},
}
eCfg.MediatorEnabled = false
eCfg.MediatorRater = utils.INTERNAL
@@ -113,13 +113,22 @@ func TestDefaults(t *testing.T) {
eCfg.SMEnabled = false
eCfg.SMSwitchType = FS
eCfg.SMRater = utils.INTERNAL
- eCfg.SMRaterReconnects = 3
+ eCfg.SMReconnects = 3
eCfg.SMDebitInterval = 10
eCfg.SMMinCallDuration = time.Duration(0)
eCfg.SMMaxCallDuration = time.Duration(3) * time.Hour
eCfg.FreeswitchServer = "127.0.0.1:8021"
eCfg.FreeswitchPass = "ClueCon"
eCfg.FreeswitchReconnects = 5
+ eCfg.FSMinDurLowBalance = time.Duration(5) * time.Second
+ eCfg.FSLowBalanceAnnFile = ""
+ eCfg.FSEmptyBalanceContext = ""
+ eCfg.FSEmptyBalanceAnnFile = ""
+ eCfg.OsipsListenUdp = "127.0.0.1:2020"
+ eCfg.OsipsMiAddr = "127.0.0.1:8020"
+ eCfg.OsipsEvSubscInterval = time.Duration(60) * time.Second
+ eCfg.OsipCDRS = "internal"
+ eCfg.OsipsReconnects = 3
eCfg.DerivedChargers = make(utils.DerivedChargers, 0)
eCfg.CombinedDerivedChargers = true
eCfg.HistoryAgentEnabled = false
@@ -153,11 +162,11 @@ func TestSanityCheck(t *testing.T) {
t.Error("Failed to detect missing CDR fields definition")
}
cfg.CdrcCdrType = utils.CSV
- cfg.CdrcCdrFields = map[string]*utils.RSRField{utils.ACCID: &utils.RSRField{Id: "test"}}
+ cfg.CdrcCdrFields = map[string][]*utils.RSRField{utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}}}
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect improper use of CDR field names")
}
- cfg.CdrcCdrFields = map[string]*utils.RSRField{"extra1": &utils.RSRField{Id: "test"}}
+ cfg.CdrcCdrFields = map[string][]*utils.RSRField{"extra1": []*utils.RSRField{&utils.RSRField{Id: "test"}}}
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect improper use of CDR field names")
}
@@ -229,35 +238,44 @@ func TestConfigFromFile(t *testing.T) {
eCfg.CdrcCdrInDir = "test"
eCfg.CdrcCdrOutDir = "test"
eCfg.CdrcSourceId = "test"
- eCfg.CdrcCdrFields = map[string]*utils.RSRField{
- utils.TOR: &utils.RSRField{Id: "test"},
- utils.ACCID: &utils.RSRField{Id: "test"},
- utils.REQTYPE: &utils.RSRField{Id: "test"},
- utils.DIRECTION: &utils.RSRField{Id: "test"},
- utils.TENANT: &utils.RSRField{Id: "test"},
- utils.CATEGORY: &utils.RSRField{Id: "test"},
- utils.ACCOUNT: &utils.RSRField{Id: "test"},
- utils.SUBJECT: &utils.RSRField{Id: "test"},
- utils.DESTINATION: &utils.RSRField{Id: "test"},
- utils.SETUP_TIME: &utils.RSRField{Id: "test"},
- utils.ANSWER_TIME: &utils.RSRField{Id: "test"},
- utils.USAGE: &utils.RSRField{Id: "test"},
- "test": &utils.RSRField{Id: "test"},
+ eCfg.CdrcCdrFields = map[string][]*utils.RSRField{
+ utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "test"}},
+ "test": []*utils.RSRField{&utils.RSRField{Id: "test"}},
}
eCfg.MediatorEnabled = true
eCfg.MediatorRater = "test"
- eCfg.MediatorRaterReconnects = 99
+ eCfg.MediatorReconnects = 99
eCfg.MediatorStats = "test"
eCfg.SMEnabled = true
eCfg.SMSwitchType = "test"
eCfg.SMRater = "test"
- eCfg.SMRaterReconnects = 99
+ eCfg.SMReconnects = 99
eCfg.SMDebitInterval = 99
- eCfg.SMMinCallDuration = time.Duration(99) * time.Second
+ eCfg.SMMinCallDuration = time.Duration(98) * time.Second
eCfg.SMMaxCallDuration = time.Duration(99) * time.Second
eCfg.FreeswitchServer = "test"
eCfg.FreeswitchPass = "test"
eCfg.FreeswitchReconnects = 99
+ eCfg.FSMinDurLowBalance = time.Duration(99) * time.Second
+ eCfg.FSLowBalanceAnnFile = "test"
+ eCfg.FSEmptyBalanceContext = "test"
+ eCfg.FSEmptyBalanceAnnFile = "test"
+ eCfg.OsipsListenUdp = "test"
+ eCfg.OsipsMiAddr = "test"
+ eCfg.OsipsEvSubscInterval = time.Duration(99) * time.Second
+ eCfg.OsipCDRS = "test"
+ eCfg.OsipsReconnects = 99
eCfg.DerivedChargers = utils.DerivedChargers{&utils.DerivedCharger{RunId: "test", RunFilters: "", ReqTypeField: "test", DirectionField: "test", TenantField: "test",
CategoryField: "test", AccountField: "test", SubjectField: "test", DestinationField: "test", SetupTimeField: "test", AnswerTimeField: "test", UsageField: "test"}}
eCfg.CombinedDerivedChargers = true
diff --git a/config/helpers.go b/config/helpers.go
index 53522e241..baeb6e422 100644
--- a/config/helpers.go
+++ b/config/helpers.go
@@ -120,8 +120,8 @@ func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err
}
func ParseCdrcCdrFields(torFld, accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld,
- setupTimeFld, answerTimeFld, durFld, extraFlds string) (map[string]*utils.RSRField, error) {
- cdrcCdrFlds := make(map[string]*utils.RSRField)
+ setupTimeFld, answerTimeFld, durFld, extraFlds string) (map[string][]*utils.RSRField, error) {
+ cdrcCdrFlds := make(map[string][]*utils.RSRField)
if len(extraFlds) != 0 {
if sepExtraFlds, err := ConfigSlice(extraFlds); err != nil {
return nil, err
@@ -131,10 +131,10 @@ func ParseCdrcCdrFields(torFld, accIdFld, reqtypeFld, directionFld, tenantFld, c
if spltLbl := strings.Split(fldStr, utils.CONCATENATED_KEY_SEP); len(spltLbl) != 2 {
return nil, fmt.Errorf("Wrong format for cdrc.extra_fields: %s", fldStr)
} else {
- if rsrFld, err := utils.NewRSRField(spltLbl[1]); err != nil {
+ if rsrFlds, err := utils.ParseRSRFields(spltLbl[1], utils.INFIELD_SEP); err != nil {
return nil, err
} else {
- cdrcCdrFlds[spltLbl[0]] = rsrFld
+ cdrcCdrFlds[spltLbl[0]] = rsrFlds
}
}
}
@@ -144,10 +144,10 @@ func ParseCdrcCdrFields(torFld, accIdFld, reqtypeFld, directionFld, tenantFld, c
utils.CATEGORY: categoryFld, utils.ACCOUNT: acntFld, utils.SUBJECT: subjectFld, utils.DESTINATION: destFld, utils.SETUP_TIME: setupTimeFld,
utils.ANSWER_TIME: answerTimeFld, utils.USAGE: durFld} {
if len(fldVal) != 0 {
- if rsrFld, err := utils.NewRSRField(fldVal); err != nil {
+ if rsrFlds, err := utils.ParseRSRFields(fldVal, utils.INFIELD_SEP); err != nil {
return nil, err
} else {
- cdrcCdrFlds[fldTag] = rsrFld
+ cdrcCdrFlds[fldTag] = rsrFlds
}
}
}
diff --git a/config/helpers_test.go b/config/helpers_test.go
index 11f0177bc..8f0d462ae 100644
--- a/config/helpers_test.go
+++ b/config/helpers_test.go
@@ -116,21 +116,21 @@ answer_time_field = answertime1
usage_field = duration1
extra_fields = extra1:extraval1,extra2:extraval1
`)
- eCdrcCdrFlds := map[string]*utils.RSRField{
- utils.TOR: &utils.RSRField{Id: "tor1"},
- utils.ACCID: &utils.RSRField{Id: "accid1"},
- utils.REQTYPE: &utils.RSRField{Id: "reqtype1"},
- utils.DIRECTION: &utils.RSRField{Id: "direction1"},
- utils.TENANT: &utils.RSRField{Id: "tenant1"},
- utils.CATEGORY: &utils.RSRField{Id: "category1"},
- utils.ACCOUNT: &utils.RSRField{Id: "account1"},
- utils.SUBJECT: &utils.RSRField{Id: "subject1"},
- utils.DESTINATION: &utils.RSRField{Id: "destination1"},
- utils.SETUP_TIME: &utils.RSRField{Id: "setuptime1"},
- utils.ANSWER_TIME: &utils.RSRField{Id: "answertime1"},
- utils.USAGE: &utils.RSRField{Id: "duration1"},
- "extra1": &utils.RSRField{Id: "extraval1"},
- "extra2": &utils.RSRField{Id: "extraval1"},
+ eCdrcCdrFlds := map[string][]*utils.RSRField{
+ utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "tor1"}},
+ utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "accid1"}},
+ utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "reqtype1"}},
+ utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "direction1"}},
+ utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "tenant1"}},
+ utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "category1"}},
+ utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "account1"}},
+ utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "subject1"}},
+ utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "destination1"}},
+ utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "setuptime1"}},
+ utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "answertime1"}},
+ utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "duration1"}},
+ "extra1": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}},
+ "extra2": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}},
}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
diff --git a/config/test_data.txt b/config/test_data.txt
index f45b32c2e..080e51800 100644
--- a/config/test_data.txt
+++ b/config/test_data.txt
@@ -87,22 +87,33 @@ extra_fields = test:test # Field identifiers of the fields to add in extra field
[mediator]
enabled = true # Starts Mediacategory service: .
rater = test # Address where to reach the Rater:
-rater_reconnects = 99 # Number of reconnects to rater before giving up.
+reconnects = 99 # Number of reconnects to rater before giving up.
stats = test # Address where to reach the stats service:
[session_manager]
enabled = true # Starts SessionManager service: .
switch_type = test # Defines the type of switch behind: .
rater = test # Address where to reach the Rater.
-rater_reconnects = 99 # Number of reconnects to rater before giving up.
+reconnects = 99 # Number of reconnects to rater before giving up.
debit_interval = 99 # Interval to perform debits on.
-max_call_duration = 99 # Maximum call duration a prepaid call can last
-min_call_duration = 99 # Only authorize calls with allowed duration bigger than this
+min_call_duration = 98 # Only authorize calls with allowed duration bigger than this
+max_call_duration = 99 # Maximum call duration a prepaid call can last
[freeswitch]
-server = test # Adress where to connect to FreeSWITCH socket.
-passwd = test # FreeSWITCH socket password.
-reconnects = 99 # Number of attempts on connect failure.
+server = test # Adress where to connect to FreeSWITCH socket.
+passwd = test # FreeSWITCH socket password.
+reconnects = 99 # Number of attempts on connect failure.
+min_dur_low_balance = 99 # Threshold which will trigger low balance warnings
+low_balance_ann_file = test # File to be played when low balance is reached
+empty_balance_context = test # If defined, call will be transfered to this context on empty balance
+empty_balance_ann_file = test # File to be played before disconnecting prepaid calls (applies only if no context defined)
+
+[opensips]
+listen_udp = test # Address where to listen for event datagrams coming from OpenSIPS
+mi_addr = test # Adress where to reach OpenSIPS mi_datagram module
+events_subscribe_interval = 99 # Automatic events subscription to OpenSIPS, 0 to disable it
+cdrs = test # Address where to reach CDR Server, empty to disable CDR processing <""|internal|127.0.0.1:2013>
+reconnects = 99 # Number of attempts on connect failure.
[derived_charging]
run_ids = test # Identifiers of additional sessions control.
diff --git a/config/xmlcdrc.go b/config/xmlcdrc.go
index 68d406622..01bdd0896 100644
--- a/config/xmlcdrc.go
+++ b/config/xmlcdrc.go
@@ -57,30 +57,30 @@ func (cdrcCfg *CgrXmlCdrcCfg) setDefaults() error {
cdrcCfg.CdrSourceId = dfCfg.CdrcSourceId
}
if len(cdrcCfg.CdrFields) == 0 {
- for key, cfgRsrField := range dfCfg.CdrcCdrFields {
- cdrcCfg.CdrFields = append(cdrcCfg.CdrFields, &CdrcField{Id: key, Value: cfgRsrField.Id, rsrField: cfgRsrField})
+ for key, cfgRsrFields := range dfCfg.CdrcCdrFields {
+ cdrcCfg.CdrFields = append(cdrcCfg.CdrFields, &CdrcField{Id: key, Value: "PLACEHOLDER", rsrFields: cfgRsrFields})
}
}
return nil
}
-func (cdrcCfg *CgrXmlCdrcCfg) CdrRSRFields() map[string]*utils.RSRField {
- rsrFields := make(map[string]*utils.RSRField)
+func (cdrcCfg *CgrXmlCdrcCfg) CdrRSRFields() map[string][]*utils.RSRField {
+ rsrFields := make(map[string][]*utils.RSRField)
for _, fld := range cdrcCfg.CdrFields {
- rsrFields[fld.Id] = fld.rsrField
+ rsrFields[fld.Id] = fld.rsrFields
}
return rsrFields
}
type CdrcField struct {
- XMLName xml.Name `xml:"field"`
- Id string `xml:"id,attr"`
- Value string `xml:"value,attr"`
- rsrField *utils.RSRField
+ XMLName xml.Name `xml:"field"`
+ Id string `xml:"id,attr"`
+ Value string `xml:"value,attr"`
+ rsrFields []*utils.RSRField
}
-func (cdrcFld *CdrcField) PopulateRSRField() (err error) {
- if cdrcFld.rsrField, err = utils.NewRSRField(cdrcFld.Value); err != nil {
+func (cdrcFld *CdrcField) PopulateRSRFields() (err error) {
+ if cdrcFld.rsrFields, err = utils.ParseRSRFields(cdrcFld.Value, utils.INFIELD_SEP); err != nil {
return err
}
return nil
diff --git a/config/xmlcdrc_test.go b/config/xmlcdrc_test.go
index 9fcd1a9ba..c7f3e8d53 100644
--- a/config/xmlcdrc_test.go
+++ b/config/xmlcdrc_test.go
@@ -30,15 +30,15 @@ var cfgDocCdrc *CgrXmlCfgDocument // Will be populated by first test
func TestPopulateRSRFIeld(t *testing.T) {
cdrcField := CdrcField{Id: "TEST1", Value: `~effective_caller_id_number:s/(\d+)/+$1/`}
- if err := cdrcField.PopulateRSRField(); err != nil {
+ if err := cdrcField.PopulateRSRFields(); err != nil {
t.Error("Unexpected error: ", err.Error())
- } else if cdrcField.rsrField == nil {
+ } else if cdrcField.rsrFields == nil {
t.Error("Failed loading the RSRField")
}
cdrcField = CdrcField{Id: "TEST2", Value: `99`}
- if err := cdrcField.PopulateRSRField(); err != nil {
+ if err := cdrcField.PopulateRSRFields(); err != nil {
t.Error("Unexpected error: ", err.Error())
- } else if cdrcField.rsrField == nil {
+ } else if cdrcField.rsrFields == nil {
t.Error("Failed loading the RSRField")
}
}
@@ -87,7 +87,7 @@ func TestParseXmlCdrcConfig(t *testing.T) {
/var/log/cgrates/cdrc/out
freeswitch_csv
-
+
@@ -123,7 +123,7 @@ func TestGetCdrcCfgs(t *testing.T) {
expectCdrc := &CgrXmlCdrcCfg{Enabled: true, CdrsAddress: "internal", CdrType: "csv", CsvSeparator: ",",
RunDelay: 0, CdrInDir: "/var/log/cgrates/cdrc/in", CdrOutDir: "/var/log/cgrates/cdrc/out", CdrSourceId: "freeswitch_csv"}
cdrFlds := []*CdrcField{
- &CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCID, Value: "0"},
+ &CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCID, Value: "0;13"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.REQTYPE, Value: "1"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DIRECTION, Value: "2"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.TENANT, Value: "3"},
@@ -137,7 +137,7 @@ func TestGetCdrcCfgs(t *testing.T) {
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr1", Value: "11"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr2", Value: "12"}}
for _, fld := range cdrFlds {
- fld.PopulateRSRField()
+ fld.PopulateRSRFields()
}
expectCdrc.CdrFields = cdrFlds
if !reflect.DeepEqual(expectCdrc, cdrcfgs["CDRC-CSV1"]) {
@@ -150,20 +150,20 @@ func TestCdrRSRFields(t *testing.T) {
if cdrcfgs == nil {
t.Error("No config instance returned")
}
- eRSRFields := map[string]*utils.RSRField{
- utils.ACCID: &utils.RSRField{Id: "0"},
- utils.REQTYPE: &utils.RSRField{Id: "1"},
- utils.DIRECTION: &utils.RSRField{Id: "2"},
- utils.TENANT: &utils.RSRField{Id: "3"},
- utils.CATEGORY: &utils.RSRField{Id: "4"},
- utils.ACCOUNT: &utils.RSRField{Id: "5"},
- utils.SUBJECT: &utils.RSRField{Id: "6"},
- utils.DESTINATION: &utils.RSRField{Id: "7"},
- utils.SETUP_TIME: &utils.RSRField{Id: "8"},
- utils.ANSWER_TIME: &utils.RSRField{Id: "9"},
- utils.USAGE: &utils.RSRField{Id: "10"},
- "extr1": &utils.RSRField{Id: "11"},
- "extr2": &utils.RSRField{Id: "12"},
+ eRSRFields := map[string][]*utils.RSRField{
+ utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "0"}, &utils.RSRField{Id: "13"}},
+ utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "1"}},
+ utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "2"}},
+ utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "3"}},
+ utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "4"}},
+ utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "5"}},
+ utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
+ utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "7"}},
+ utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "8"}},
+ utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "9"}},
+ utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "10"}},
+ "extr1": []*utils.RSRField{&utils.RSRField{Id: "11"}},
+ "extr2": []*utils.RSRField{&utils.RSRField{Id: "12"}},
}
if rsrFields := cdrcfgs["CDRC-CSV1"].CdrRSRFields(); !reflect.DeepEqual(rsrFields, eRSRFields) {
t.Errorf("Expecting: %v, received: %v", eRSRFields, rsrFields)
diff --git a/config/xmlconfig.go b/config/xmlconfig.go
index 906947c3f..5c2c903cd 100644
--- a/config/xmlconfig.go
+++ b/config/xmlconfig.go
@@ -85,7 +85,7 @@ func (xmlCfg *CgrXmlCfgDocument) cacheCdrcCfgs() error {
}
// Cache rsr fields
for _, fld := range cdrcCfg.CdrFields {
- if err := fld.PopulateRSRField(); err != nil {
+ if err := fld.PopulateRSRFields(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Id, err.Error())
}
}
diff --git a/data/conf/cgrates.cfg b/data/conf/cgrates.cfg
index ece39cde0..b4a117534 100644
--- a/data/conf/cgrates.cfg
+++ b/data/conf/cgrates.cfg
@@ -1,5 +1,5 @@
# Real-time Charging System for Telecom & ISP environments
-# Copyright (C) 2012-2014 ITsysCOM GmbH
+# Copyright (C) ITsysCOM GmbH
#
# This file contains the default configuration hardcoded into CGRateS.
# This is what you get when you load CGRateS with an empty configuration file.
@@ -90,23 +90,33 @@
# rater_reconnects = 3 # Number of reconnects to rater before giving up.
[session_manager]
-# enabled = false # Starts SessionManager service: .
-# switch_type = freeswitch # Defines the type of switch behind: .
-# rater = internal # Address where to reach the Rater.
-# rater_reconnects = 3 # Number of reconnects to rater before giving up.
+# enabled = false # Starts SessionManager service:
+# switch_type = freeswitch # Defines the type of switch behind:
+# rater = internal # Address where to reach the Rater
+# reconnects = 3 # Number of reconnects to rater/cdrs before giving up.
# debit_interval = 10 # Interval to perform debits on.
-# min_call_duration = 0s # Only authorize calls with allowed duration bigger than this
+# min_call_duration = 0s # Only authorize calls with allowed duration bigger than this
# max_call_duration = 3h # Maximum call duration a prepaid call can last
-
[freeswitch]
# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket.
# passwd = ClueCon # FreeSWITCH socket password.
# reconnects = 5 # Number of attempts on connect failure.
+# min_dur_low_balance = 5s # Threshold which will trigger low balance warnings for prepaid calls (needs to be lower than debit_interval)
+# low_balance_ann_file = # File to be played when low balance is reached for prepaid calls
+# empty_balance_context = # If defined, prepaid calls will be transfered to this context on empty balance
+# empty_balance_ann_file = # File to be played before disconnecting prepaid calls on empty balance (applies only if no context defined)
+
+[opensips]
+# listen_udp = 127.0.0.1:2020 # Address where to listen for datagram events coming from OpenSIPS
+# mi_addr = 127.0.0.1:8020 # Adress where to reach OpenSIPS mi_datagram module
+# events_subscribe_interval = 60s # Automatic events subscription to OpenSIPS, 0 to disable it
+# cdrs = internal # Address where to reach CDR Server, empty to disable CDR processing <""|internal|127.0.0.1:2013>
+# reconnects = 3 # Number of attempts on connect failure.
[derived_charging]
# run_ids = # Identifiers of additional sessions control.
-# run_filters = # List of cdr field filters for each run.
+# run_filters = # List of cdr field filters for each run.
# reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
# direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
# tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
diff --git a/data/storage/mysql/create_cdrs_tables.sql b/data/storage/mysql/create_cdrs_tables.sql
index 8765d1d46..b8d2312e8 100644
--- a/data/storage/mysql/create_cdrs_tables.sql
+++ b/data/storage/mysql/create_cdrs_tables.sql
@@ -34,7 +34,8 @@ CREATE TABLE cdrs_extra (
tbid int(11) NOT NULL AUTO_INCREMENT,
cgrid char(40) NOT NULL,
extra_fields text NOT NULL,
- PRIMARY KEY (tbid)
+ PRIMARY KEY (tbid),
+ UNIQUE KEY cgrid (cgrid)
);
--
diff --git a/cdrs/cdrs.go b/engine/cdrs.go
similarity index 77%
rename from cdrs/cdrs.go
rename to engine/cdrs.go
index df3b44163..e449d1038 100644
--- a/cdrs/cdrs.go
+++ b/engine/cdrs.go
@@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-package cdrs
+package engine
import (
"fmt"
@@ -25,14 +25,13 @@ import (
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
- "github.com/cgrates/cgrates/mediator"
"github.com/cgrates/cgrates/utils"
)
var (
cfg *config.CGRConfig // Share the configuration with the rest of the package
storage engine.CdrStorage
- medi *mediator.Mediator
+ medi *Mediator
stats engine.StatsInterface
)
@@ -51,7 +50,7 @@ func storeAndMediate(storedCdr *utils.StoredCdr) error {
if cfg.CDRSMediator == utils.INTERNAL {
go func() {
if err := medi.RateCdr(storedCdr, true); err != nil {
- engine.Logger.Err(fmt.Sprintf("Could not run mediation on CDR: %s", err.Error()))
+ Logger.Err(fmt.Sprintf("Could not run mediation on CDR: %s", err.Error()))
}
}()
}
@@ -62,10 +61,10 @@ func storeAndMediate(storedCdr *utils.StoredCdr) error {
func cgrCdrHandler(w http.ResponseWriter, r *http.Request) {
cgrCdr, err := utils.NewCgrCdrFromHttpReq(r)
if err != nil {
- engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error()))
+ Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error()))
}
if err := storeAndMediate(cgrCdr.AsStoredCdr()); err != nil {
- engine.Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error()))
+ Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error()))
}
}
@@ -74,16 +73,16 @@ func fsCdrHandler(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body)
fsCdr, err := NewFSCdr(body)
if err != nil {
- engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error()))
+ Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error()))
}
if err := storeAndMediate(fsCdr.AsStoredCdr()); err != nil {
- engine.Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error()))
+ Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error()))
}
}
type CDRS struct{}
-func New(s engine.CdrStorage, m *mediator.Mediator, st *engine.Stats, c *config.CGRConfig) *CDRS {
+func NewCdrS(s CdrStorage, m *Mediator, st *Stats, c *config.CGRConfig) *CDRS {
storage = s
medi = m
cfg = c
@@ -103,12 +102,12 @@ func New(s engine.CdrStorage, m *mediator.Mediator, st *engine.Stats, c *config.
return &CDRS{}
}
-func (cdrs *CDRS) RegisterHanlersToServer(server *engine.Server) {
+func (cdrs *CDRS) RegisterHanlersToServer(server *Server) {
server.RegisterHttpFunc("/cgr", cgrCdrHandler)
server.RegisterHttpFunc("/freeswitch_json", fsCdrHandler)
}
// Used to internally process CDR
-func (cdrs *CDRS) ProcessRawCdr(rawCdr utils.RawCdr) error {
- return storeAndMediate(rawCdr.AsStoredCdr())
+func (cdrs *CDRS) ProcessCdr(cdr *utils.StoredCdr) error {
+ return storeAndMediate(cdr)
}
diff --git a/cdrs/fscdr.go b/engine/fscdr.go
similarity index 95%
rename from cdrs/fscdr.go
rename to engine/fscdr.go
index f7437661c..417d96b58 100644
--- a/cdrs/fscdr.go
+++ b/engine/fscdr.go
@@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-package cdrs
+package engine
import (
"encoding/json"
@@ -24,7 +24,6 @@ import (
"reflect"
"strings"
- "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -111,11 +110,11 @@ func (fsCdr FSCdr) searchExtraField(field string, body map[string]interface{}) (
return
}
} else {
- engine.Logger.Warning(fmt.Sprintf("Slice with no maps: %v", reflect.TypeOf(item)))
+ Logger.Warning(fmt.Sprintf("Slice with no maps: %v", reflect.TypeOf(item)))
}
}
default:
- engine.Logger.Warning(fmt.Sprintf("Unexpected type: %v", reflect.TypeOf(v)))
+ Logger.Warning(fmt.Sprintf("Unexpected type: %v", reflect.TypeOf(v)))
}
}
return
diff --git a/cdrs/fscdr_test.go b/engine/fscdr_test.go
similarity index 99%
rename from cdrs/fscdr_test.go
rename to engine/fscdr_test.go
index b26995c26..b137ecf3f 100644
--- a/cdrs/fscdr_test.go
+++ b/engine/fscdr_test.go
@@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-package cdrs
+package engine
import (
"reflect"
diff --git a/engine/loader_local_test.go b/engine/loader_local_test.go
index 7baa27baa..9997bbb2b 100644
--- a/engine/loader_local_test.go
+++ b/engine/loader_local_test.go
@@ -44,7 +44,7 @@ README:
var ratingDbCsv, ratingDbStor, ratingDbApier RatingStorage // Each ratingDb will have it's own sources to collect data
var accountDbCsv, accountDbStor, accountDbApier AccountingStorage // Each ratingDb will have it's own sources to collect data
var storDb LoadStorage
-var cfg *config.CGRConfig
+var lCfg *config.CGRConfig
// Arguments received via test command
var testLocal = flag.Bool("local", false, "Perform the tests only on local test environment, not by default.") // This flag will be passed here via "go test -local" args
@@ -57,27 +57,27 @@ func TestConnDataDbs(t *testing.T) {
if !*testLocal {
return
}
- cfg, _ = config.NewDefaultCGRConfig()
+ lCfg, _ = config.NewDefaultCGRConfig()
var err error
- if ratingDbCsv, err = ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort, "4", cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding); err != nil {
+ if ratingDbCsv, err = ConfigureRatingStorage(lCfg.RatingDBType, lCfg.RatingDBHost, lCfg.RatingDBPort, "4", lCfg.RatingDBUser, lCfg.RatingDBPass, lCfg.DBDataEncoding); err != nil {
t.Fatal("Error on ratingDb connection: ", err.Error())
}
- if ratingDbStor, err = ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort, "5", cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding); err != nil {
+ if ratingDbStor, err = ConfigureRatingStorage(lCfg.RatingDBType, lCfg.RatingDBHost, lCfg.RatingDBPort, "5", lCfg.RatingDBUser, lCfg.RatingDBPass, lCfg.DBDataEncoding); err != nil {
t.Fatal("Error on ratingDb connection: ", err.Error())
}
- if ratingDbApier, err = ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort, "6", cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding); err != nil {
+ if ratingDbApier, err = ConfigureRatingStorage(lCfg.RatingDBType, lCfg.RatingDBHost, lCfg.RatingDBPort, "6", lCfg.RatingDBUser, lCfg.RatingDBPass, lCfg.DBDataEncoding); err != nil {
t.Fatal("Error on ratingDb connection: ", err.Error())
}
- if accountDbCsv, err = ConfigureAccountingStorage(cfg.AccountDBType, cfg.AccountDBHost, cfg.AccountDBPort, "7",
- cfg.AccountDBUser, cfg.AccountDBPass, cfg.DBDataEncoding); err != nil {
+ if accountDbCsv, err = ConfigureAccountingStorage(lCfg.AccountDBType, lCfg.AccountDBHost, lCfg.AccountDBPort, "7",
+ lCfg.AccountDBUser, lCfg.AccountDBPass, lCfg.DBDataEncoding); err != nil {
t.Fatal("Error on ratingDb connection: ", err.Error())
}
- if accountDbStor, err = ConfigureAccountingStorage(cfg.AccountDBType, cfg.AccountDBHost, cfg.AccountDBPort, "8",
- cfg.AccountDBUser, cfg.AccountDBPass, cfg.DBDataEncoding); err != nil {
+ if accountDbStor, err = ConfigureAccountingStorage(lCfg.AccountDBType, lCfg.AccountDBHost, lCfg.AccountDBPort, "8",
+ lCfg.AccountDBUser, lCfg.AccountDBPass, lCfg.DBDataEncoding); err != nil {
t.Fatal("Error on ratingDb connection: ", err.Error())
}
- if accountDbApier, err = ConfigureAccountingStorage(cfg.AccountDBType, cfg.AccountDBHost, cfg.AccountDBPort, "9",
- cfg.AccountDBUser, cfg.AccountDBPass, cfg.DBDataEncoding); err != nil {
+ if accountDbApier, err = ConfigureAccountingStorage(lCfg.AccountDBType, lCfg.AccountDBHost, lCfg.AccountDBPort, "9",
+ lCfg.AccountDBUser, lCfg.AccountDBPass, lCfg.DBDataEncoding); err != nil {
t.Fatal("Error on ratingDb connection: ", err.Error())
}
for _, db := range []Storage{ratingDbCsv, ratingDbStor, ratingDbApier, accountDbCsv, accountDbStor, accountDbApier} {
@@ -94,7 +94,7 @@ func TestCreateStorTpTables(t *testing.T) {
return
}
var db *MySQLStorage
- if d, err := NewMySQLStorage(cfg.StorDBHost, cfg.StorDBPort, cfg.StorDBName, cfg.StorDBUser, cfg.StorDBPass); err != nil {
+ if d, err := NewMySQLStorage(lCfg.StorDBHost, lCfg.StorDBPort, lCfg.StorDBName, lCfg.StorDBUser, lCfg.StorDBPass); err != nil {
t.Error("Error on opening database connection: ", err)
return
} else {
diff --git a/mediator/mediator.go b/engine/mediator.go
similarity index 80%
rename from mediator/mediator.go
rename to engine/mediator.go
index dc732f08d..2ee412986 100644
--- a/mediator/mediator.go
+++ b/engine/mediator.go
@@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-package mediator
+package engine
import (
"errors"
@@ -28,7 +28,7 @@ import (
"github.com/cgrates/cgrates/utils"
)
-func NewMediator(connector engine.Connector, logDb engine.LogStorage, cdrDb engine.CdrStorage, st engine.StatsInterface, cfg *config.CGRConfig) (m *Mediator, err error) {
+func NewMediator(connector Connector, logDb LogStorage, cdrDb CdrStorage, st StatsInterface, cfg *config.CGRConfig) (m *Mediator, err error) {
m = &Mediator{
connector: connector,
logDb: logDb,
@@ -52,17 +52,17 @@ func NewMediator(connector engine.Connector, logDb engine.LogStorage, cdrDb engi
}
type Mediator struct {
- connector engine.Connector
- logDb engine.LogStorage
- cdrDb engine.CdrStorage
- stats engine.StatsInterface
+ connector Connector
+ logDb LogStorage
+ cdrDb CdrStorage
+ stats StatsInterface
cgrCfg *config.CGRConfig
}
// Retrive the cost from logging database, nil in case of no log
-func (self *Mediator) getCostsFromDB(cgrid, runId string) (cc *engine.CallCost, err error) {
+func (self *Mediator) getCostsFromDB(cgrid, runId string) (cc *CallCost, err error) {
for i := 0; i < 3; i++ { // Mechanism to avoid concurrency between SessionManager writing the costs and mediator picking them up
- cc, err = self.logDb.GetCallCostLog(cgrid, engine.SESSION_MANAGER_SOURCE, runId)
+ cc, err = self.logDb.GetCallCostLog(cgrid, SESSION_MANAGER_SOURCE, runId)
if cc != nil {
break
}
@@ -72,13 +72,13 @@ func (self *Mediator) getCostsFromDB(cgrid, runId string) (cc *engine.CallCost,
}
// Retrive the cost from engine
-func (self *Mediator) getCostFromRater(storedCdr *utils.StoredCdr) (*engine.CallCost, error) {
- cc := &engine.CallCost{}
+func (self *Mediator) getCostFromRater(storedCdr *utils.StoredCdr) (*CallCost, error) {
+ cc := &CallCost{}
var err error
if storedCdr.Usage == time.Duration(0) { // failed call, returning empty callcost, no error
return cc, nil
}
- cd := engine.CallDescriptor{
+ cd := CallDescriptor{
TOR: storedCdr.TOR,
Direction: storedCdr.Direction,
Tenant: storedCdr.Tenant,
@@ -96,16 +96,16 @@ func (self *Mediator) getCostFromRater(storedCdr *utils.StoredCdr) (*engine.Call
err = self.connector.GetCost(cd, cc)
}
if err != nil {
- self.logDb.LogError(storedCdr.CgrId, engine.MEDIATOR_SOURCE, storedCdr.MediationRunId, err.Error())
+ self.logDb.LogError(storedCdr.CgrId, MEDIATOR_SOURCE, storedCdr.MediationRunId, err.Error())
} else {
// If the mediator calculated a price it will write it to logdb
- self.logDb.LogCallCost(storedCdr.CgrId, engine.MEDIATOR_SOURCE, storedCdr.MediationRunId, cc)
+ self.logDb.LogCallCost(storedCdr.CgrId, MEDIATOR_SOURCE, storedCdr.MediationRunId, cc)
}
return cc, err
}
func (self *Mediator) rateCDR(storedCdr *utils.StoredCdr) error {
- var qryCC *engine.CallCost
+ var qryCC *CallCost
var errCost error
if storedCdr.ReqType == utils.PREPAID {
// Should be previously calculated and stored in DB
@@ -130,7 +130,7 @@ func (self *Mediator) RateCdr(storedCdr *utils.StoredCdr, sendToStats bool) erro
var dcs utils.DerivedChargers
if err := self.connector.GetDerivedChargers(attrsDC, &dcs); err != nil {
errText := fmt.Sprintf("Could not get derived charging for cgrid %s, error: %s", storedCdr.CgrId, err.Error())
- engine.Logger.Err(errText)
+ Logger.Err(errText)
return errors.New(errText)
}
for _, dc := range dcs {
@@ -177,15 +177,17 @@ func (self *Mediator) RateCdr(storedCdr *utils.StoredCdr, sendToStats bool) erro
}()
}
if err := self.cdrDb.SetRatedCdr(cdr, extraInfo); err != nil {
- engine.Logger.Err(fmt.Sprintf(" Could not record cost for cgrid: <%s>, ERROR: <%s>, cost: %f, extraInfo: %s",
+ Logger.Err(fmt.Sprintf(" Could not record cost for cgrid: <%s>, ERROR: <%s>, cost: %f, extraInfo: %s",
cdr.CgrId, err.Error(), cdr.Cost, extraInfo))
}
}
return nil
}
-func (self *Mediator) RateCdrs(timeStart, timeEnd time.Time, rerateErrors, rerateRated bool, sentToStats bool) error {
- cdrs, err := self.cdrDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, !rerateErrors, !rerateRated, true)
+func (self *Mediator) RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes, ratedAccounts, ratedSubjects []string,
+ orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated bool, sendToStats bool) error {
+ cdrs, err := self.cdrDb.GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes, ratedAccounts, ratedSubjects,
+ orderIdStart, orderIdEnd, timeStart, timeEnd, !rerateErrors, !rerateRated, true)
if err != nil {
return err
}
diff --git a/mediator/mediator_local_test.go b/engine/mediator_local_test.go
similarity index 84%
rename from mediator/mediator_local_test.go
rename to engine/mediator_local_test.go
index 3ca4f84a1..05fcf0405 100644
--- a/mediator/mediator_local_test.go
+++ b/engine/mediator_local_test.go
@@ -16,13 +16,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-package mediator
+package engine
import (
"flag"
"fmt"
"net/http"
"net/rpc"
+ "net/rpc/jsonrpc"
"net/url"
"os/exec"
"path"
@@ -30,7 +31,6 @@ import (
"time"
"github.com/cgrates/cgrates/config"
- "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -48,13 +48,11 @@ README:
* Execute remote Apis and test their replies(follow prepaid1cent scenario so we can test load in dataDb also).
*/
-var cfg *config.CGRConfig
+var cgrCfg *config.CGRConfig
var cgrRpc *rpc.Client
-var cdrStor engine.CdrStorage
+var cdrStor CdrStorage
var httpClient *http.Client
-var testLocal = flag.Bool("local", false, "Perform the tests only on local test environment, not by default.") // This flag will be passed here via "go test -local" args
-var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here")
var storDbType = flag.String("stordb_type", utils.MYSQL, "The type of the storDb database ")
var startDelay = flag.Int("delay_start", 300, "Number of miliseconds to it for rater to start and cache")
var cfgPath = path.Join(*dataDir, "conf", "samples", "mediator_test1.cfg")
@@ -64,11 +62,11 @@ func TestInitRatingDb(t *testing.T) {
return
}
var err error
- cfg, err = config.NewCGRConfigFromFile(&cfgPath)
+ cgrCfg, err = config.NewCGRConfigFromFile(&cfgPath)
if err != nil {
t.Fatal("Got config error: ", err.Error())
}
- ratingDb, err := engine.ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort, cfg.RatingDBName, cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding)
+ ratingDb, err := ConfigureRatingStorage(cgrCfg.RatingDBType, cgrCfg.RatingDBHost, cgrCfg.RatingDBPort, cgrCfg.RatingDBName, cgrCfg.RatingDBUser, cgrCfg.RatingDBPass, cgrCfg.DBDataEncoding)
if err != nil {
t.Fatal("Cannot connect to dataDb", err)
}
@@ -85,14 +83,14 @@ func TestInitStorDb(t *testing.T) {
if *storDbType != utils.MYSQL {
t.Fatal("Unsupported storDbType")
}
- var mysql *engine.MySQLStorage
+ var mysql *MySQLStorage
var err error
- if cdrStor, err = engine.ConfigureCdrStorage(cfg.StorDBType, cfg.StorDBHost, cfg.StorDBPort, cfg.StorDBName, cfg.StorDBUser, cfg.StorDBPass); err != nil {
+ if cdrStor, err = ConfigureCdrStorage(cgrCfg.StorDBType, cgrCfg.StorDBHost, cgrCfg.StorDBPort, cgrCfg.StorDBName, cgrCfg.StorDBUser, cgrCfg.StorDBPass); err != nil {
t.Fatal("Error on opening database connection: ", err)
} else {
- mysql = cdrStor.(*engine.MySQLStorage)
+ mysql = cdrStor.(*MySQLStorage)
}
- if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, engine.CREATE_CDRS_TABLES_SQL)); err != nil {
+ if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, CREATE_CDRS_TABLES_SQL)); err != nil {
t.Fatal("Error on mysql creation: ", err.Error())
return // No point in going further
}
@@ -127,7 +125,8 @@ func TestRpcConn(t *testing.T) {
return
}
var err error
- cgrRpc, err = rpc.Dial("tcp", cfg.RPCGOBListen) //ToDo: Fix with automatic config
+ //cgrRpc, err = rpc.Dial("tcp", cfg.RPCGOBListen) //ToDo: Fix with automatic config
+ cgrRpc, err = jsonrpc.Dial("tcp", cgrCfg.RPCJSONListen)
if err != nil {
t.Fatal("Could not connect to CGR GOB-RPC Server: ", err.Error())
}
@@ -149,18 +148,18 @@ func TestPostCdrs(t *testing.T) {
utils.ACCOUNT: []string{"1010"}, utils.SUBJECT: []string{"1010"}, utils.ANSWER_TIME: []string{"2013-11-07T08:42:26Z"},
utils.USAGE: []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}}
for _, cdrForm := range []url.Values{cdrForm1, cdrForm2, cdrFormData1} {
- cdrForm.Set(utils.CDRSOURCE, engine.TEST_SQL)
- if _, err := httpClient.PostForm(fmt.Sprintf("http://%s/cgr", cfg.HTTPListen), cdrForm); err != nil {
+ cdrForm.Set(utils.CDRSOURCE, TEST_SQL)
+ if _, err := httpClient.PostForm(fmt.Sprintf("http://%s/cgr", cgrCfg.HTTPListen), cdrForm); err != nil {
t.Error(err.Error())
}
}
time.Sleep(100 * time.Millisecond) // Give time for CDRs to reach database
- if storedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, false, false); err != nil {
+ if storedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, false, false); err != nil {
t.Error(err)
} else if len(storedCdrs) != 6 { // Make sure CDRs made it into StorDb
t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(storedCdrs)))
}
- if nonErrorCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, false, false); err != nil {
+ if nonErrorCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, false, false); err != nil {
t.Error(err)
} else if len(nonErrorCdrs) != 0 {
t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(nonErrorCdrs)))
@@ -172,10 +171,10 @@ func TestInjectCdrs(t *testing.T) {
if !*testLocal {
return
}
- cgrCdr1 := utils.CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "aaaaadsafdsaf", "cdrsource": engine.TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: "rated", utils.DIRECTION: "*out",
+ cgrCdr1 := utils.CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "aaaaadsafdsaf", "cdrsource": TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: "rated", utils.DIRECTION: "*out",
utils.TENANT: "cgrates.org", utils.CATEGORY: "call", utils.ACCOUNT: "dan", utils.SUBJECT: "dan", utils.DESTINATION: "+4986517174963",
utils.ANSWER_TIME: "2013-11-07T08:42:26Z", utils.USAGE: "10"}
- cgrCdr2 := utils.CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "baaaadsafdsaf", "cdrsource": engine.TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: "rated", utils.DIRECTION: "*out",
+ cgrCdr2 := utils.CgrCdr{utils.TOR: utils.VOICE, utils.ACCID: "baaaadsafdsaf", "cdrsource": TEST_SQL, utils.CDRHOST: "192.168.1.1", utils.REQTYPE: "rated", utils.DIRECTION: "*out",
utils.TENANT: "cgrates.org", utils.CATEGORY: "call", utils.ACCOUNT: "dan", utils.SUBJECT: "dan", utils.DESTINATION: "+4986517173964",
utils.ANSWER_TIME: "2013-11-07T09:42:26Z", utils.USAGE: "20"}
for _, cdr := range []utils.CgrCdr{cgrCdr1, cgrCdr2} {
@@ -183,12 +182,12 @@ func TestInjectCdrs(t *testing.T) {
t.Error(err)
}
}
- if storedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, false, false); err != nil {
+ if storedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, false, false); err != nil {
t.Error(err)
} else if len(storedCdrs) != 8 { // Make sure CDRs made it into StorDb
t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(storedCdrs)))
}
- if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true, false); err != nil {
+ if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true, false); err != nil {
t.Error(err)
} else if len(nonRatedCdrs) != 2 { // Just two of them should be non-rated
t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs)))
@@ -220,12 +219,12 @@ func TestRateCdrs(t *testing.T) {
} else if reply != utils.OK {
t.Errorf("Unexpected reply: %s", reply)
}
- if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true, false); err != nil {
+ if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true, false); err != nil {
t.Error(err)
} else if len(nonRatedCdrs) != 0 { // All CDRs should be rated
t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs)))
}
- if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true, false); err != nil {
+ if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true, false); err != nil {
t.Error(err)
} else if len(errRatedCdrs) != 8 { // The first 2 with errors should be still there before rerating
t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs)))
@@ -235,7 +234,7 @@ func TestRateCdrs(t *testing.T) {
} else if reply != utils.OK {
t.Errorf("Unexpected reply: %s", reply)
}
- if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true, false); err != nil {
+ if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true, false); err != nil {
t.Error(err)
} else if len(errRatedCdrs) != 4 {
t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs)))
diff --git a/engine/responder.go b/engine/responder.go
index d0b879411..906a343a7 100644
--- a/engine/responder.go
+++ b/engine/responder.go
@@ -36,6 +36,7 @@ import (
type Responder struct {
Bal *balancer2go.Balancer
ExitChan chan bool
+ CdrSrv *CDRS
}
/*
@@ -128,6 +129,17 @@ func (rs *Responder) GetDerivedChargers(attrs utils.AttrDerivedChargers, dcs *ut
return nil
}
+func (rs *Responder) ProcessCdr(cdr *utils.StoredCdr, reply *string) error {
+ if rs.CdrSrv == nil {
+ return errors.New("CdrServerNotRunning")
+ }
+ if err := rs.CdrSrv.ProcessCdr(cdr); err != nil {
+ return err
+ }
+ *reply = utils.OK
+ return nil
+}
+
func (rs *Responder) FlushCache(arg CallDescriptor, reply *float64) (err error) {
if rs.Bal != nil {
*reply, err = rs.callMethod(&arg, "Responder.FlushCache")
@@ -287,6 +299,7 @@ type Connector interface {
RefundIncrements(CallDescriptor, *float64) error
GetMaxSessionTime(CallDescriptor, *float64) error
GetDerivedChargers(utils.AttrDerivedChargers, *utils.DerivedChargers) error
+ ProcessCdr(*utils.StoredCdr, *string) error
}
type RPCClientConnector struct {
@@ -316,3 +329,7 @@ func (rcc *RPCClientConnector) GetMaxSessionTime(cd CallDescriptor, resp *float6
func (rcc *RPCClientConnector) GetDerivedChargers(attrs utils.AttrDerivedChargers, dcs *utils.DerivedChargers) error {
return rcc.Client.Call("ApierV1.GetDerivedChargers", attrs, dcs)
}
+
+func (rcc *RPCClientConnector) ProcessCdr(cdr *utils.StoredCdr, reply *string) error {
+ return rcc.Client.Call("CDRSV1.ProcessCdr", cdr, reply)
+}
diff --git a/engine/storage_interface.go b/engine/storage_interface.go
index 48548b0e7..b0f3929e7 100644
--- a/engine/storage_interface.go
+++ b/engine/storage_interface.go
@@ -116,7 +116,7 @@ type CdrStorage interface {
Storage
SetCdr(*utils.StoredCdr) error
SetRatedCdr(*utils.StoredCdr, string) error
- GetStoredCdrs([]string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string,
+ GetStoredCdrs([]string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string,
int64, int64, time.Time, time.Time, bool, bool, bool) ([]*utils.StoredCdr, error)
RemStoredCdrs([]string) error
}
diff --git a/engine/storage_sql.go b/engine/storage_sql.go
index 7120a367b..bf4ce822b 100644
--- a/engine/storage_sql.go
+++ b/engine/storage_sql.go
@@ -609,12 +609,12 @@ func (self *SQLStorage) SetRatedCdr(storedCdr *utils.StoredCdr, extraInfo string
// Return a slice of CDRs from storDb using optional filters.a
// ignoreErr - do not consider cdrs with rating errors
// ignoreRated - do not consider cdrs which were already rated, including here the ones with errors
-func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string, orderIdStart, orderIdEnd int64,
- timeStart, timeEnd time.Time, ignoreErr, ignoreRated, ignoreDerived bool) ([]*utils.StoredCdr, error) {
+func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes, ratedAccounts, ratedSubjects []string,
+ orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, ignoreErr, ignoreRated, ignoreDerived bool) ([]*utils.StoredCdr, error) {
var cdrs []*utils.StoredCdr
var q *bytes.Buffer // Need to query differently since in case of primary, unmediated CDRs some values will be missing
if ignoreDerived {
- q = bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.`usage`,%s.extra_fields,%s.runid,%s.cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid",
+ q = bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.`usage`,%s.extra_fields,%s.runid,%s.account,%s.subject,%s.cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid AND %s.runid=%s.runid",
utils.TBL_CDRS_PRIMARY,
utils.TBL_CDRS_PRIMARY,
utils.TBL_CDRS_PRIMARY,
@@ -633,6 +633,8 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources
utils.TBL_CDRS_PRIMARY,
utils.TBL_CDRS_EXTRA,
utils.TBL_RATED_CDRS,
+ utils.TBL_COST_DETAILS,
+ utils.TBL_COST_DETAILS,
utils.TBL_RATED_CDRS,
utils.TBL_CDRS_PRIMARY,
utils.TBL_CDRS_EXTRA,
@@ -640,9 +642,14 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources
utils.TBL_CDRS_EXTRA,
utils.TBL_RATED_CDRS,
utils.TBL_CDRS_PRIMARY,
- utils.TBL_RATED_CDRS))
+ utils.TBL_RATED_CDRS,
+ utils.TBL_COST_DETAILS,
+ utils.TBL_RATED_CDRS,
+ utils.TBL_COST_DETAILS,
+ utils.TBL_RATED_CDRS,
+ utils.TBL_COST_DETAILS))
} else {
- q = bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.`usage`,%s.extra_fields,%s.runid,%s.cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid",
+ q = bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.`usage`,%s.extra_fields,%s.runid,%s.account,%s.subject,%s.cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid AND %s.runid=%s.runid",
utils.TBL_CDRS_PRIMARY,
utils.TBL_CDRS_PRIMARY,
utils.TBL_CDRS_PRIMARY,
@@ -661,6 +668,8 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources
utils.TBL_RATED_CDRS,
utils.TBL_CDRS_EXTRA,
utils.TBL_RATED_CDRS,
+ utils.TBL_COST_DETAILS,
+ utils.TBL_COST_DETAILS,
utils.TBL_RATED_CDRS,
utils.TBL_CDRS_PRIMARY,
utils.TBL_CDRS_EXTRA,
@@ -668,7 +677,12 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources
utils.TBL_CDRS_EXTRA,
utils.TBL_RATED_CDRS,
utils.TBL_CDRS_PRIMARY,
- utils.TBL_RATED_CDRS))
+ utils.TBL_RATED_CDRS,
+ utils.TBL_COST_DETAILS,
+ utils.TBL_RATED_CDRS,
+ utils.TBL_COST_DETAILS,
+ utils.TBL_RATED_CDRS,
+ utils.TBL_COST_DETAILS))
}
fltr := new(bytes.Buffer)
if len(cgrIds) != 0 {
@@ -839,6 +853,34 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources
}
fltr.Write(qIds.Bytes())
}
+ if len(ratedAccounts) != 0 {
+ qIds := bytes.NewBufferString(" (")
+ for idx, ratedAccount := range ratedAccounts {
+ if idx != 0 {
+ qIds.WriteString(" OR")
+ }
+ qIds.WriteString(fmt.Sprintf(" %s.account='%s'", utils.TBL_COST_DETAILS, ratedAccount))
+ }
+ qIds.WriteString(" )")
+ if fltr.Len() != 0 {
+ fltr.WriteString(" AND")
+ }
+ fltr.Write(qIds.Bytes())
+ }
+ if len(ratedSubjects) != 0 {
+ qIds := bytes.NewBufferString(" (")
+ for idx, ratedSubject := range ratedSubjects {
+ if idx != 0 {
+ qIds.WriteString(" OR")
+ }
+ qIds.WriteString(fmt.Sprintf(" %s.subject='%s'", utils.TBL_COST_DETAILS, ratedSubject))
+ }
+ qIds.WriteString(" )")
+ if fltr.Len() != 0 {
+ fltr.WriteString(" AND")
+ }
+ fltr.Write(qIds.Bytes())
+ }
if orderIdStart != 0 {
if fltr.Len() != 0 {
fltr.WriteString(" AND")
@@ -893,14 +935,14 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources
}
defer rows.Close()
for rows.Next() {
- var cgrid, tor, accid, cdrhost, cdrsrc, reqtype, direction, tenant, category, account, subject, destination, runid sql.NullString
+ var cgrid, tor, accid, cdrhost, cdrsrc, reqtype, direction, tenant, category, account, subject, destination, runid, ratedAccount, ratedSubject sql.NullString
var extraFields []byte
var setupTime, answerTime mysql.NullTime
var orderid int64
var usage, cost sql.NullFloat64
var extraFieldsMp map[string]string
if err := rows.Scan(&cgrid, &orderid, &tor, &accid, &cdrhost, &cdrsrc, &reqtype, &direction, &tenant, &category, &account, &subject, &destination, &setupTime, &answerTime, &usage,
- &extraFields, &runid, &cost); err != nil {
+ &extraFields, &runid, &ratedAccount, &ratedSubject, &cost); err != nil {
return nil, err
}
if len(extraFields) != 0 {
@@ -914,7 +956,7 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources
Direction: direction.String, Tenant: tenant.String,
Category: category.String, Account: account.String, Subject: subject.String, Destination: destination.String,
SetupTime: setupTime.Time, AnswerTime: answerTime.Time, Usage: usageDur,
- ExtraFields: extraFieldsMp, MediationRunId: runid.String, Cost: cost.Float64,
+ ExtraFields: extraFieldsMp, MediationRunId: runid.String, RatedAccount: ratedAccount.String, RatedSubject: ratedSubject.String, Cost: cost.Float64,
}
if !cost.Valid { //There was no cost provided, will fakely insert 0 if we do not handle it and reflect on re-rating
storCdr.Cost = -1
diff --git a/engine/storage_sql_local_test.go b/engine/storage_sql_local_test.go
index 4ff49b062..316f1194a 100644
--- a/engine/storage_sql_local_test.go
+++ b/engine/storage_sql_local_test.go
@@ -217,13 +217,47 @@ func TestSetRatedCdr(t *testing.T) {
}
}
+func TestCallCost(t *testing.T) {
+ if !*testLocal {
+ return
+ }
+ cgrId := utils.Sha1("bbb1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String())
+ cc := &CallCost{
+ Direction: "*out",
+ Category: "call",
+ Tenant: "cgrates.org",
+ Subject: "91001",
+ Account: "8001",
+ Destination: "1002",
+ TOR: utils.VOICE,
+ Timespans: []*TimeSpan{
+ &TimeSpan{
+ TimeStart: time.Date(2013, 9, 10, 13, 40, 0, 0, time.UTC),
+ TimeEnd: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC),
+ },
+ &TimeSpan{
+ TimeStart: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC),
+ TimeEnd: time.Date(2013, 9, 10, 13, 41, 30, 0, time.UTC),
+ },
+ },
+ }
+ if err := mysqlDb.LogCallCost(cgrId, TEST_SQL, utils.DEFAULT_RUNID, cc); err != nil {
+ t.Error(err.Error())
+ }
+ if ccRcv, err := mysqlDb.GetCallCostLog(cgrId, TEST_SQL, utils.DEFAULT_RUNID); err != nil {
+ t.Error(err.Error())
+ } else if !reflect.DeepEqual(cc, ccRcv) {
+ t.Errorf("Expecting call cost: %v, received: %v", cc, ccRcv)
+ }
+}
+
func TestGetStoredCdrs(t *testing.T) {
if !*testLocal {
return
}
var timeStart, timeEnd time.Time
// All CDRs, no filter
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
@@ -231,7 +265,7 @@ func TestGetStoredCdrs(t *testing.T) {
// Filter on cgrids
if storedCdrs, err := mysqlDb.GetStoredCdrs([]string{utils.Sha1("bbb1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()),
utils.Sha1("bbb2", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String())},
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 2 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
@@ -239,41 +273,41 @@ func TestGetStoredCdrs(t *testing.T) {
// Filter on cgrids plus reqType
if storedCdrs, err := mysqlDb.GetStoredCdrs([]string{utils.Sha1("bbb1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()),
utils.Sha1("bbb2", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String())},
- nil, nil, nil, nil, []string{"prepaid"}, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, []string{"prepaid"}, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 1 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on runId
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, []string{utils.DEFAULT_RUNID},
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 2 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on TOR
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, []string{utils.SMS},
- nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 0 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple TOR
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, []string{utils.SMS, utils.VOICE},
- nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on cdrHost
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, []string{"192.168.1.2"},
- nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 3 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple cdrHost
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, []string{"192.168.1.1", "192.168.1.2"}, nil, nil, nil, nil, nil, nil, nil, nil,
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, []string{"192.168.1.1", "192.168.1.2"}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
@@ -281,46 +315,46 @@ func TestGetStoredCdrs(t *testing.T) {
}
// Filter on cdrSource
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, []string{"UNKNOWN"},
- nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 1 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple cdrSource
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, []string{"UNKNOWN", "UNKNOWN2"},
- nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 2 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on reqType
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, []string{"prepaid"},
- nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 2 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple reqType
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, []string{"prepaid", "pseudoprepaid"}, nil, nil, nil, nil, nil, nil,
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, []string{"prepaid", "pseudoprepaid"}, nil, nil, nil, nil, nil, nil, nil, nil,
0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 3 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on direction
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, []string{"*out"}, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, []string{"*out"}, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on tenant
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, []string{"itsyscom.com"}, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, []string{"itsyscom.com"}, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 3 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple tenants
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, []string{"itsyscom.com", "cgrates.org"}, nil, nil, nil, nil,
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, []string{"itsyscom.com", "cgrates.org"}, nil, nil, nil, nil, nil, nil,
0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
@@ -328,69 +362,83 @@ func TestGetStoredCdrs(t *testing.T) {
}
// Filter on tor
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, []string{"premium_call"},
- nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 1 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple tor
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, []string{"premium_call", "call"},
- nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on account
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1002"},
- nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 3 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple account
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1001", "1002"},
- nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on subject
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1000"},
- nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 1 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple subject
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1000", "1002"},
- nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 3 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on destPrefix
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, []string{"+498651"}, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, nil, nil, []string{"+498651"}, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 3 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on multiple destPrefixes
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1001", "+498651"},
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []string{"1001", "+498651"}, nil, nil,
0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 4 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
+ // Filter on ratedAccount
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, []string{"8001"}, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ t.Error(err.Error())
+ } else if len(storedCdrs) != 1 {
+ t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
+ }
+ // Filter on ratedSubject
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, []string{"91001"}, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ t.Error(err.Error())
+ } else if len(storedCdrs) != 1 {
+ t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
+ }
// Filter on ignoreErr
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, true, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, true, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on ignoreRated
var orderIdStart, orderIdEnd int64 // Capture also orderIds for the next test
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, true, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, true, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 5 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
@@ -405,13 +453,13 @@ func TestGetStoredCdrs(t *testing.T) {
}
}
// Filter on orderIdStart
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, orderIdStart, 0, timeStart, timeEnd, false, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, orderIdStart, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 8 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on orderIdStart and orderIdEnd
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, orderIdStart, orderIdEnd+1, timeStart, timeEnd,
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, orderIdStart, orderIdEnd+1, timeStart, timeEnd,
false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 5 {
@@ -419,60 +467,33 @@ func TestGetStoredCdrs(t *testing.T) {
}
// Filter on timeStart
timeStart = time.Date(2013, 11, 8, 8, 0, 0, 0, time.UTC)
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 5 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on timeStart and timeEnd
timeEnd = time.Date(2013, 12, 1, 8, 0, 0, 0, time.UTC)
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 2 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Combined filter
if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, []string{"rated"}, nil, nil, nil, nil, nil,
- nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 1 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
// Filter on ignoreDerived
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, true); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, true); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 2 { // ToDo: Recheck this value
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
}
}
-func TestCallCost(t *testing.T) {
- if !*testLocal {
- return
- }
- cgrId := utils.Sha1("bbb1", "123")
- cc := &CallCost{
- Timespans: []*TimeSpan{
- &TimeSpan{
- TimeStart: time.Date(2013, 9, 10, 13, 40, 0, 0, time.UTC),
- TimeEnd: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC),
- },
- &TimeSpan{
- TimeStart: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC),
- TimeEnd: time.Date(2013, 9, 10, 13, 41, 30, 0, time.UTC),
- },
- },
- }
- if err := mysqlDb.LogCallCost(cgrId, TEST_SQL, TEST_SQL, cc); err != nil {
- t.Error(err.Error())
- }
- if ccRcv, err := mysqlDb.GetCallCostLog(cgrId, TEST_SQL, TEST_SQL); err != nil {
- t.Error(err.Error())
- } else if !reflect.DeepEqual(cc, ccRcv) {
- t.Errorf("Expecting call cost: %v, received: %v", cc, ccRcv)
- }
-}
-
func TestRemStoredCdrs(t *testing.T) {
if !*testLocal {
return
@@ -482,7 +503,7 @@ func TestRemStoredCdrs(t *testing.T) {
if err := mysqlDb.RemStoredCdrs([]string{cgrIdB1}); err != nil {
t.Error(err.Error())
}
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 7 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
@@ -503,7 +524,7 @@ func TestRemStoredCdrs(t *testing.T) {
cgrIdB2, cgrIdB3}); err != nil {
t.Error(err.Error())
}
- if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
+ if storedCdrs, err := mysqlDb.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, timeStart, timeEnd, false, false, false); err != nil {
t.Error(err.Error())
} else if len(storedCdrs) != 0 {
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
diff --git a/general_tests/fsevcorelate_test.go b/general_tests/fsevcorelate_test.go
index bccdcba41..394080910 100644
--- a/general_tests/fsevcorelate_test.go
+++ b/general_tests/fsevcorelate_test.go
@@ -21,8 +21,8 @@ package general_tests
import (
"testing"
- "github.com/cgrates/cgrates/cdrs"
"github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/sessionmanager"
)
@@ -215,12 +215,12 @@ var jsonCdr = []byte(`{"core-uuid":"feef0b51-7fdf-4c4a-878e-aff233752de2","chann
func TestEvCorelate(t *testing.T) {
cfg, _ := config.NewDefaultCGRConfig()
- cdrs.New(nil, nil, nil, cfg) // So we can set the package cfg
+ engine.NewCdrS(nil, nil, nil, cfg) // So we can set the package cfg
answerEv := new(sessionmanager.FSEvent).New(answerEvent)
if answerEv.GetName() != "CHANNEL_ANSWER" {
t.Error("Event not parsed correctly: ", answerEv)
}
- cdrEv, err := cdrs.NewFSCdr(jsonCdr)
+ cdrEv, err := engine.NewFSCdr(jsonCdr)
if err != nil {
t.Errorf("Error loading cdr: %v", err.Error())
} else if cdrEv.AsStoredCdr().AccId != "86cfd6e2-dbda-45a3-b59d-f683ec368e8b" {
diff --git a/local_test.sh b/local_test.sh
index fbfea7c13..b34696cb6 100755
--- a/local_test.sh
+++ b/local_test.sh
@@ -8,8 +8,6 @@ go test github.com/cgrates/cgrates/engine -local
en=$?
go test github.com/cgrates/cgrates/cdrc -local
cdrc=$?
-go test github.com/cgrates/cgrates/mediator -local
-med=$?
go test github.com/cgrates/cgrates/config -local
cfg=$?
go test github.com/cgrates/cgrates/utils -local
@@ -20,5 +18,5 @@ utl=$?
-exit $gen && $ap && $en && $cdrc && $med && $cfg && $utl
+exit $gen && $ap && $en && $cdrc && $cfg && $utl
diff --git a/mediator/fsfilecsvcdr.go b/mediator/fsfilecsvcdr.go
deleted file mode 100644
index be3e004b3..000000000
--- a/mediator/fsfilecsvcdr.go
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
-Rating system designed to be used in VoIP Carriers World
-Copyright (C) 2013 ITsysCOM
-
-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 mediator
-
-import (
- "github.com/cgrates/cgrates/config"
- "github.com/cgrates/cgrates/utils"
- "strconv"
- "time"
-)
-
-type FScsvCDR struct {
- rowData []string // The original row extracted form csv file
- accIdIdx,
- subjectIdx,
- reqtypeIdx,
- directionIdx,
- tenantIdx,
- torIdx,
- accountIdx,
- destinationIdx,
- setupTimeIdx,
- answerTimeIdx,
- durationIdx int // Field indexes
- cgrCfg *config.CGRConfig // CGR Config instance
-}
-
-func NewFScsvCDR(cdrRow []string, accIdIdx, subjectIdx, reqtypeIdx, directionIdx, tenantIdx, torIdx,
- accountIdx, destinationIdx, setupTimeIdx, answerTimeIdx, durationIdx int, cfg *config.CGRConfig) (*FScsvCDR, error) {
- fscdr := FScsvCDR{cdrRow, accIdIdx, subjectIdx, reqtypeIdx, directionIdx, tenantIdx,
- torIdx, accountIdx, destinationIdx, setupTimeIdx, answerTimeIdx, durationIdx, cfg}
- return &fscdr, nil
-}
-
-func (self *FScsvCDR) GetCgrId() string {
- return utils.Sha1(self.rowData[self.accIdIdx], self.rowData[self.setupTimeIdx])
-}
-
-func (self *FScsvCDR) GetAccId() string {
- return self.rowData[self.accIdIdx]
-}
-
-func (self *FScsvCDR) GetCdrHost() string {
- return utils.LOCALHOST // ToDo: Maybe extract dynamically the external IP address here
-}
-
-func (self *FScsvCDR) GetDirection() string {
- return "*out"
-}
-
-func (self *FScsvCDR) GetSubject() string {
- return self.rowData[self.subjectIdx]
-}
-
-func (self *FScsvCDR) GetAccount() string {
- return self.rowData[self.accountIdx]
-}
-
-func (self *FScsvCDR) GetDestination() string {
- return self.rowData[self.destinationIdx]
-}
-
-func (self *FScsvCDR) GetTOR() string {
- return self.rowData[self.torIdx]
-}
-
-func (self *FScsvCDR) GetTenant() string {
- return self.rowData[self.tenantIdx]
-}
-
-func (self *FScsvCDR) GetReqType() string {
- if self.reqtypeIdx == -1 {
- return self.cgrCfg.DefaultReqType
- }
- return self.rowData[self.reqtypeIdx]
-}
-
-func (self *FScsvCDR) GetAnswerTime() (time.Time, error) {
- return time.Parse("2006-01-02 15:04:05", self.rowData[self.answerTimeIdx])
-}
-
-func (self *FScsvCDR) GetDuration() int64 {
- dur, _ := strconv.ParseInt(self.rowData[self.durationIdx], 0, 64)
- return dur
-}
-
-func (self *FScsvCDR) GetExtraFields() map[string]string {
- return nil
-}
diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go
index 412715fe5..6b924cee6 100644
--- a/sessionmanager/fssessionmanager.go
+++ b/sessionmanager/fssessionmanager.go
@@ -1,6 +1,6 @@
/*
Real-time Charging System for Telecom & ISP environments
-Copyright (C) 2012-2014 ITsysCOM GmbH
+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
@@ -45,14 +45,14 @@ type FSSessionManager struct {
loggerDB engine.LogStorage
}
-func NewFSSessionManager(storage engine.LogStorage, connector engine.Connector, debitPeriod time.Duration) *FSSessionManager {
+func NewFSSessionManager(cgrCfg *config.CGRConfig, storage engine.LogStorage, connector engine.Connector, debitPeriod time.Duration) *FSSessionManager {
+ cfg = cgrCfg // make config global
return &FSSessionManager{loggerDB: storage, connector: connector, debitPeriod: debitPeriod}
}
// Connects to the freeswitch mod_event_socket server and starts
// listening for events.
-func (sm *FSSessionManager) Connect(cgrCfg *config.CGRConfig) (err error) {
- cfg = cgrCfg // make config global
+func (sm *FSSessionManager) Connect() (err error) {
eventFilters := map[string]string{"Call-Direction": "inbound"}
if fsock.FS, err = fsock.NewFSock(cfg.FreeswitchServer, cfg.FreeswitchPass, cfg.FreeswitchReconnects, sm.createHandlers(), eventFilters, engine.Logger.(*syslog.Writer)); err != nil {
return err
@@ -99,14 +99,24 @@ func (sm *FSSessionManager) GetSession(uuid string) *Session {
}
// Disconnects a session by sending hangup command to freeswitch
-func (sm *FSSessionManager) DisconnectSession(uuid string, notify string) {
- // engine.Logger.Debug(fmt.Sprintf("Session: %+v", s.uuid))
- _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s cgr_notify %s\n\n", uuid, notify))
- if err != nil {
- engine.Logger.Err(fmt.Sprintf(" Could not send disconect api notification to freeswitch: %v", err))
+func (sm *FSSessionManager) DisconnectSession(uuid, notify, destnr string) {
+ if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s cgr_notify %s\n\n", uuid, notify)); err != nil {
+ engine.Logger.Err(fmt.Sprintf(" Could not send disconect api notification to freeswitch: %s", err.Error()))
}
- err = fsock.FS.SendMsgCmd(uuid, map[string]string{"call-command": "hangup", "hangup-cause": "MANAGER_REQUEST"}) // without + sign
- if err != nil {
+ if notify == INSUFFICIENT_FUNDS {
+ if len(cfg.FSEmptyBalanceContext) != 0 {
+ if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_transfer %s %s %s\n\n", uuid, destnr, cfg.FSEmptyBalanceContext)); err != nil {
+ engine.Logger.Err(" Could not transfer the call to empty balance context")
+ }
+ return
+ } else if len(cfg.FSEmptyBalanceAnnFile) != 0 {
+ if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_broadcast %s playback!manager_request::%s aleg\n\n", uuid, cfg.FSEmptyBalanceAnnFile)); err != nil {
+ engine.Logger.Err(fmt.Sprintf(" Could not send uuid_broadcast to freeswitch: %s", err.Error()))
+ }
+ return
+ }
+ }
+ if err := fsock.FS.SendMsgCmd(uuid, map[string]string{"call-command": "hangup", "hangup-cause": "MANAGER_REQUEST"}); err != nil {
engine.Logger.Err(fmt.Sprintf(" Could not send disconect msg to freeswitch: %v", err))
}
return
@@ -124,7 +134,8 @@ func (sm *FSSessionManager) RemoveSession(uuid string) {
// Sets the call timeout valid of starting of the call
func (sm *FSSessionManager) setMaxCallDuration(uuid string, maxDur time.Duration) error {
- _, err := fsock.FS.SendApiCmd(fmt.Sprintf("sched_hangup +%d %s\n\n", int(maxDur.Seconds()), uuid))
+ // _, err := fsock.FS.SendApiCmd(fmt.Sprintf("sched_hangup +%d %s\n\n", int(maxDur.Seconds()), uuid))
+ _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s execute_on_answer sched_hangup +%d alloted_timeout\n\n", uuid, int(maxDur.Seconds())))
if err != nil {
engine.Logger.Err("could not send sched_hangup command to freeswitch")
return err
@@ -138,8 +149,7 @@ func (sm *FSSessionManager) unparkCall(uuid, call_dest_nb, notify string) {
if err != nil {
engine.Logger.Err(" Could not send unpark api notification to freeswitch")
}
- _, err = fsock.FS.SendApiCmd(fmt.Sprintf("uuid_transfer %s %s\n\n", uuid, call_dest_nb))
- if err != nil {
+ if _, err = fsock.FS.SendApiCmd(fmt.Sprintf("uuid_transfer %s %s\n\n", uuid, call_dest_nb)); err != nil {
engine.Logger.Err(" Could not send unpark api call to freeswitch")
}
}
@@ -223,7 +233,7 @@ func (sm *FSSessionManager) OnChannelPark(ev Event) {
func (sm *FSSessionManager) OnChannelAnswer(ev Event) {
if ev.MissingParameter() {
- sm.DisconnectSession(ev.GetUUID(), MISSING_PARAMETER)
+ sm.DisconnectSession(ev.GetUUID(), MISSING_PARAMETER, "")
}
if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s cgr_reqtype %s\n\n", ev.GetUUID(), ev.GetReqType(""))); err != nil {
engine.Logger.Err(fmt.Sprintf("Error on attempting to overwrite cgr_type in chan variables: %v", err))
@@ -233,7 +243,7 @@ func (sm *FSSessionManager) OnChannelAnswer(ev Event) {
var dcs utils.DerivedChargers
if err := sm.connector.GetDerivedChargers(attrsDC, &dcs); err != nil {
engine.Logger.Err(fmt.Sprintf(" OnAnswer: could not get derived charging for event %s: %s", ev.GetUUID(), err.Error()))
- sm.DisconnectSession(ev.GetUUID(), SYSTEM_ERROR) // Disconnect the session since we are not able to process sessions
+ sm.DisconnectSession(ev.GetUUID(), SYSTEM_ERROR, "") // Disconnect the session since we are not able to process sessions
return
}
dcs, _ = dcs.AppendDefaultRun()
diff --git a/sessionmanager/osipsevent.go b/sessionmanager/osipsevent.go
new file mode 100644
index 000000000..37431f991
--- /dev/null
+++ b/sessionmanager/osipsevent.go
@@ -0,0 +1,217 @@
+/*
+Real-time Charging System 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/cgrates/utils"
+ "github.com/cgrates/osipsdagram"
+ "strings"
+ "time"
+)
+
+/*
+/*&{Name:E_ACC_CDR AttrValues:map[to_tag:5ec6e925 cgr_account:dan setuptime:1 created:1406312794 method:INVITE callid:Y2I5ZDYzMDkzM2YzYjhlZjA2Y2ZhZTJmZTc4MGU4NDI.
+ // sip_reason:OK time:1406312795 cgr_reqtype:prepaid cgr_destination:dan cgr_subject:dan sip_code:200 duration:7 from_tag:a5716471] Values:[]}*/
+
+const (
+ FROM_TAG = "from_tag"
+ TO_TAG = "to_tag"
+ CALLID = "callid"
+ CGR_CATEGORY = "cgr_category"
+ CGR_REQTYPE = "cgr_reqtype"
+ CGR_TENANT = "cgr_tenant"
+ CGR_SUBJECT = "cgr_subject"
+ CGR_ACCOUNT = "cgr_account"
+ CGR_DESTINATION = "cgr_destination"
+ TIME = "time"
+ SETUP_DURATION = "setuptime"
+ OSIPS__SETUP_TIME = "created"
+ OSIPS_DURATION = "duration"
+)
+
+func NewOsipsEvent(osipsDagramEvent *osipsdagram.OsipsEvent) (*OsipsEvent, error) {
+ return &OsipsEvent{osipsEvent: osipsDagramEvent}, nil
+}
+
+type OsipsEvent struct {
+ osipsEvent *osipsdagram.OsipsEvent
+}
+
+func (osipsev *OsipsEvent) New(evStr string) Event {
+ return osipsev
+}
+
+func (osipsev *OsipsEvent) GetName() string {
+ return osipsev.osipsEvent.Name
+}
+
+func (osipsev *OsipsEvent) GetCgrId() string {
+ setupTime, _ := osipsev.GetSetupTime(utils.META_DEFAULT)
+ return utils.Sha1(osipsev.GetUUID(), setupTime.UTC().String())
+}
+
+func (osipsev *OsipsEvent) GetUUID() string {
+ return osipsev.osipsEvent.AttrValues[CALLID] + ";" + osipsev.osipsEvent.AttrValues[FROM_TAG] + ";" + osipsev.osipsEvent.AttrValues[TO_TAG]
+}
+
+func (osipsev *OsipsEvent) GetDirection(fieldName string) string {
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ return fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ }
+ return utils.OUT
+}
+
+func (osipsev *OsipsEvent) GetSubject(fieldName string) string {
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ return fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ }
+ return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_SUBJECT])
+}
+
+func (osipsev *OsipsEvent) GetAccount(fieldName string) string {
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ return fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ }
+ return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_ACCOUNT])
+}
+
+func (osipsev *OsipsEvent) GetDestination(fieldName string) string {
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ return fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ }
+ return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_DESTINATION])
+}
+
+func (osipsev *OsipsEvent) GetCallDestNr(fieldName string) string {
+ return osipsev.GetDestination(fieldName)
+}
+
+func (osipsev *OsipsEvent) GetCategory(fieldName string) string {
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ return fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ }
+ return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_CATEGORY], config.CgrConfig().DefaultCategory)
+}
+
+func (osipsev *OsipsEvent) GetTenant(fieldName string) string {
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ return fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ }
+ return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_TENANT], config.CgrConfig().DefaultTenant)
+}
+func (osipsev *OsipsEvent) GetReqType(fieldName string) string {
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ return fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ }
+ return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_REQTYPE], config.CgrConfig().DefaultReqType)
+}
+func (osipsev *OsipsEvent) GetSetupTime(fieldName string) (time.Time, error) {
+ sTimeStr := utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[OSIPS__SETUP_TIME])
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ sTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ } else if fieldName == utils.META_DEFAULT {
+ sTimeStr = osipsev.osipsEvent.AttrValues[OSIPS__SETUP_TIME]
+ }
+ return utils.ParseTimeDetectLayout(sTimeStr)
+}
+func (osipsev *OsipsEvent) GetAnswerTime(fieldName string) (time.Time, error) {
+ aTimeStr := utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[TIME])
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ aTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ } else if fieldName == utils.META_DEFAULT {
+ aTimeStr = osipsev.osipsEvent.AttrValues[TIME]
+ }
+ return utils.ParseTimeDetectLayout(aTimeStr)
+}
+func (osipsev *OsipsEvent) GetEndTime() (time.Time, error) {
+ var nilTime time.Time
+ aTime, err := osipsev.GetAnswerTime(utils.META_DEFAULT)
+ if err != nil {
+ return nilTime, err
+ }
+ dur, err := osipsev.GetDuration(utils.META_DEFAULT)
+ if err != nil {
+ return nilTime, err
+ }
+ return aTime.Add(dur), nil
+}
+func (osipsev *OsipsEvent) GetDuration(fieldName string) (time.Duration, error) {
+ durStr := utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[OSIPS_DURATION])
+ if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
+ durStr = fieldName[len(utils.STATIC_VALUE_PREFIX):]
+ }
+ return utils.ParseDurationWithSecs(durStr)
+}
+func (osipsev *OsipsEvent) MissingParameter() bool {
+ var nilTime time.Time
+ var nilDur time.Duration
+ aTime, _ := osipsev.GetAnswerTime(utils.META_DEFAULT)
+ dur, _ := osipsev.GetDuration(utils.META_DEFAULT)
+ return len(osipsev.GetUUID()) == 0 ||
+ len(osipsev.GetAccount(utils.META_DEFAULT)) == 0 ||
+ len(osipsev.GetSubject(utils.META_DEFAULT)) == 0 ||
+ len(osipsev.GetDestination(utils.META_DEFAULT)) == 0 ||
+ aTime == nilTime ||
+ dur == nilDur
+}
+func (osipsev *OsipsEvent) ParseEventValue(*utils.RSRField) string {
+ return ""
+}
+func (osipsev *OsipsEvent) PassesFieldFilter(*utils.RSRField) (bool, string) {
+ return false, ""
+}
+func (osipsev *OsipsEvent) GetExtraFields() map[string]string {
+ primaryFields := []string{"to_tag", "setuptime", "created", "method", "callid", "sip_reason", "time", "sip_code", "duration", "from_tag",
+ "cgr_tenant", "cgr_category", "cgr_reqtype", "cgr_account", "cgr_subject", "cgr_destination"}
+ extraFields := make(map[string]string)
+ for field, val := range osipsev.osipsEvent.AttrValues {
+ if !utils.IsSliceMember(primaryFields, field) {
+ extraFields[field] = val
+ }
+ }
+ return extraFields
+}
+func (osipsEv *OsipsEvent) GetOriginatorIP() string {
+ if osipsEv.osipsEvent == nil || osipsEv.osipsEvent.OriginatorAddress == nil {
+ return ""
+ }
+ return osipsEv.osipsEvent.OriginatorAddress.IP.String()
+}
+func (osipsEv *OsipsEvent) AsStoredCdr() *utils.StoredCdr {
+ storCdr := new(utils.StoredCdr)
+ storCdr.CgrId = osipsEv.GetCgrId()
+ storCdr.TOR = utils.VOICE
+ storCdr.AccId = osipsEv.GetUUID()
+ storCdr.CdrHost = osipsEv.GetOriginatorIP()
+ storCdr.CdrSource = "OSIPS_" + osipsEv.GetName()
+ storCdr.ReqType = osipsEv.GetReqType(utils.META_DEFAULT)
+ storCdr.Direction = osipsEv.GetDirection(utils.META_DEFAULT)
+ storCdr.Tenant = osipsEv.GetTenant(utils.META_DEFAULT)
+ storCdr.Category = osipsEv.GetCategory(utils.META_DEFAULT)
+ storCdr.Account = osipsEv.GetAccount(utils.META_DEFAULT)
+ storCdr.Subject = osipsEv.GetSubject(utils.META_DEFAULT)
+ storCdr.Destination = osipsEv.GetDestination(utils.META_DEFAULT)
+ storCdr.SetupTime, _ = osipsEv.GetSetupTime(utils.META_DEFAULT)
+ storCdr.AnswerTime, _ = osipsEv.GetAnswerTime(utils.META_DEFAULT)
+ storCdr.Usage, _ = osipsEv.GetDuration(utils.META_DEFAULT)
+ storCdr.ExtraFields = osipsEv.GetExtraFields()
+ storCdr.Cost = -1
+ return storCdr
+}
diff --git a/sessionmanager/osipsevent_test.go b/sessionmanager/osipsevent_test.go
new file mode 100644
index 000000000..5925341a5
--- /dev/null
+++ b/sessionmanager/osipsevent_test.go
@@ -0,0 +1,135 @@
+/*
+Real-time Charging System 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 (
+ "net"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/utils"
+ "github.com/cgrates/osipsdagram"
+)
+
+var addr, _ = net.ResolveUDPAddr("udp", "172.16.254.77:42574")
+var osipsEv = &OsipsEvent{osipsEvent: &osipsdagram.OsipsEvent{Name: "E_ACC_CDR",
+ AttrValues: map[string]string{"to_tag": "4ea9687f", "cgr_account": "dan", "setuptime": "7", "created": "1406370492", "method": "INVITE", "callid": "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ",
+ "sip_reason": "OK", "time": "1406370499", "cgr_reqtype": "prepaid", "cgr_subject": "dan", "cgr_destination": "+4986517174963", "cgr_tenant": "itsyscom.com", "sip_code": "200",
+ "duration": "20", "from_tag": "eb082607", "extra1": "val1", "extra2": "val2"}, OriginatorAddress: addr}}
+
+func TestOsipsEventInterface(t *testing.T) {
+ var _ Event = Event(osipsEv)
+}
+
+func TestOsipsEventParseStatic(t *testing.T) {
+ setupTime, _ := osipsEv.GetSetupTime("^2013-12-07 08:42:24")
+ answerTime, _ := osipsEv.GetAnswerTime("^2013-12-07 08:42:24")
+ dur, _ := osipsEv.GetDuration("^60s")
+ if osipsEv.GetReqType("^test") != "test" ||
+ osipsEv.GetDirection("^test") != "test" ||
+ osipsEv.GetTenant("^test") != "test" ||
+ osipsEv.GetCategory("^test") != "test" ||
+ osipsEv.GetAccount("^test") != "test" ||
+ osipsEv.GetSubject("^test") != "test" ||
+ osipsEv.GetDestination("^test") != "test" ||
+ setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) ||
+ answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) ||
+ dur != time.Duration(60)*time.Second {
+ t.Error("Values out of static not matching",
+ osipsEv.GetReqType("^test") != "test",
+ osipsEv.GetDirection("^test") != "test",
+ osipsEv.GetTenant("^test") != "test",
+ osipsEv.GetCategory("^test") != "test",
+ osipsEv.GetAccount("^test") != "test",
+ osipsEv.GetSubject("^test") != "test",
+ osipsEv.GetDestination("^test") != "test",
+ setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC),
+ answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC),
+ dur != time.Duration(60)*time.Second)
+ }
+}
+
+func TestOsipsEventGetValues(t *testing.T) {
+ cfg, _ = config.NewDefaultCGRConfig()
+ config.SetCgrConfig(cfg)
+ setupTime, _ := osipsEv.GetSetupTime(utils.META_DEFAULT)
+ answerTime, _ := osipsEv.GetAnswerTime(utils.META_DEFAULT)
+ endTime, _ := osipsEv.GetEndTime()
+ dur, _ := osipsEv.GetDuration(utils.META_DEFAULT)
+ if osipsEv.GetName() != "E_ACC_CDR" ||
+ osipsEv.GetCgrId() != utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ"+";"+"eb082607"+";"+"4ea9687f", setupTime.UTC().String()) ||
+ osipsEv.GetUUID() != "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f" ||
+ osipsEv.GetDirection(utils.META_DEFAULT) != utils.OUT ||
+ osipsEv.GetSubject(utils.META_DEFAULT) != "dan" ||
+ osipsEv.GetAccount(utils.META_DEFAULT) != "dan" ||
+ osipsEv.GetDestination(utils.META_DEFAULT) != "+4986517174963" ||
+ osipsEv.GetCallDestNr(utils.META_DEFAULT) != "+4986517174963" ||
+ osipsEv.GetCategory(utils.META_DEFAULT) != cfg.DefaultCategory ||
+ osipsEv.GetTenant(utils.META_DEFAULT) != "itsyscom.com" ||
+ osipsEv.GetReqType(utils.META_DEFAULT) != "prepaid" ||
+ setupTime != time.Date(2014, 7, 26, 12, 28, 12, 0, time.Local) ||
+ answerTime != time.Date(2014, 7, 26, 12, 28, 19, 0, time.Local) ||
+ endTime != time.Date(2014, 7, 26, 12, 28, 39, 0, time.Local) ||
+ dur != time.Duration(20*time.Second) ||
+ osipsEv.GetOriginatorIP() != "172.16.254.77" {
+ t.Error("GetValues not matching: ", osipsEv.GetName() != "E_ACC_CDR",
+ osipsEv.GetCgrId() != utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ"+";"+"eb082607"+";"+"4ea9687f", setupTime.UTC().String()),
+ osipsEv.GetUUID() != "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f",
+ osipsEv.GetDirection(utils.META_DEFAULT) != utils.OUT,
+ osipsEv.GetSubject(utils.META_DEFAULT) != "dan",
+ osipsEv.GetAccount(utils.META_DEFAULT) != "dan",
+ osipsEv.GetDestination(utils.META_DEFAULT) != "+4986517174963",
+ osipsEv.GetCallDestNr(utils.META_DEFAULT) != "+4986517174963",
+ osipsEv.GetCategory(utils.META_DEFAULT) != cfg.DefaultCategory,
+ osipsEv.GetTenant(utils.META_DEFAULT) != "itsyscom.com",
+ osipsEv.GetReqType(utils.META_DEFAULT) != "prepaid",
+ setupTime != time.Date(2014, 7, 26, 12, 28, 12, 0, time.Local),
+ answerTime != time.Date(2014, 7, 26, 12, 28, 19, 0, time.Local),
+ endTime != time.Date(2014, 7, 26, 12, 28, 39, 0, time.Local),
+ dur != time.Duration(20*time.Second),
+ osipsEv.GetOriginatorIP() != "172.16.254.77",
+ )
+ }
+}
+
+func TestOsipsEventMissingParameter(t *testing.T) {
+ if osipsEv.MissingParameter() {
+ t.Errorf("Wrongly detected missing parameter: %+v", osipsEv)
+ }
+ osipsEv2 := &OsipsEvent{osipsEvent: &osipsdagram.OsipsEvent{Name: "E_ACC_CDR",
+ AttrValues: map[string]string{"to_tag": "4ea9687f", "cgr_account": "dan", "setuptime": "7", "created": "1406370492", "method": "INVITE", "callid": "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ",
+ "sip_reason": "OK", "time": "1406370499", "cgr_reqtype": "prepaid", "cgr_subject": "dan", "cgr_tenant": "itsyscom.com", "sip_code": "200",
+ "duration": "20", "from_tag": "eb082607"}}}
+ if !osipsEv2.MissingParameter() {
+ t.Error("Failed to detect missing parameter.")
+ }
+}
+
+func TestOsipsEventAsStoredCdr(t *testing.T) {
+ eStoredCdr := &utils.StoredCdr{CgrId: utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f", time.Date(2014, 7, 26, 12, 28, 12, 0, time.Local).UTC().String()),
+ TOR: utils.VOICE, AccId: "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f", CdrHost: "172.16.254.77", CdrSource: "OSIPS_E_ACC_CDR", ReqType: "prepaid",
+ Direction: utils.OUT, Tenant: "itsyscom.com", Category: "call", Account: "dan", Subject: "dan",
+ Destination: "+4986517174963", SetupTime: time.Date(2014, 7, 26, 12, 28, 12, 0, time.Local), AnswerTime: time.Date(2014, 7, 26, 12, 28, 19, 0, time.Local),
+ Usage: time.Duration(20) * time.Second, ExtraFields: map[string]string{"extra1": "val1", "extra2": "val2"}, Cost: -1}
+ if storedCdr := osipsEv.AsStoredCdr(); !reflect.DeepEqual(eStoredCdr, storedCdr) {
+ t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr)
+ }
+}
diff --git a/sessionmanager/osipssm.go b/sessionmanager/osipssm.go
new file mode 100644
index 000000000..20db4cac8
--- /dev/null
+++ b/sessionmanager/osipssm.go
@@ -0,0 +1,146 @@
+/*
+Real-time Charging System 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 (
+ "bytes"
+ "errors"
+ "fmt"
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/osipsdagram"
+ "strings"
+ "time"
+)
+
+func NewOSipsSessionManager(cfg *config.CGRConfig, cdrsrv engine.Connector) (*OsipsSessionManager, error) {
+ osm := &OsipsSessionManager{cgrCfg: cfg, cdrsrv: cdrsrv}
+ osm.eventHandlers = map[string][]func(*osipsdagram.OsipsEvent){
+ "E_OPENSIPS_START": []func(*osipsdagram.OsipsEvent){osm.OnOpensipsStart},
+ "E_ACC_CDR": []func(*osipsdagram.OsipsEvent){osm.OnCdr},
+ }
+ return osm, nil
+}
+
+type OsipsSessionManager struct {
+ cgrCfg *config.CGRConfig
+ cdrsrv engine.Connector
+ eventHandlers map[string][]func(*osipsdagram.OsipsEvent)
+ evSubscribeStop *chan struct{} // Reference towards the channel controlling subscriptions, keep it as reference so we do not need to copy it
+ stopServing chan struct{} // Stop serving datagrams
+ miConn *osipsdagram.OsipsMiDatagramConnector
+}
+
+func (osm *OsipsSessionManager) Connect() (err error) {
+ osm.stopServing = make(chan struct{})
+ if osm.miConn, err = osipsdagram.NewOsipsMiDatagramConnector(osm.cgrCfg.OsipsMiAddr, osm.cgrCfg.OsipsReconnects); err != nil {
+ return fmt.Errorf("Cannot connect to OpenSIPS at %s, error: %s", osm.cgrCfg.OsipsMiAddr, err.Error())
+ }
+ evSubscribeStop := make(chan struct{})
+ osm.evSubscribeStop = &evSubscribeStop
+ defer close(*osm.evSubscribeStop) // Stop subscribing on disconnect
+ go osm.SubscribeEvents(evSubscribeStop)
+ evsrv, err := osipsdagram.NewEventServer(osm.cgrCfg.OsipsListenUdp, osm.eventHandlers)
+ if err != nil {
+ engine.Logger.Err(fmt.Sprintf(" Cannot initialize datagram server, error: <%s>", err.Error()))
+ return
+ }
+ engine.Logger.Info(fmt.Sprintf(" Listening for datagram events at <%s>", osm.cgrCfg.OsipsListenUdp))
+ evsrv.ServeEvents(osm.stopServing) // Will break through stopServing on error in other places
+ return errors.New(" Stopped reading events")
+}
+
+func (osm *OsipsSessionManager) DisconnectSession(uuid, notify, destnr string) {
+ return
+}
+func (osm *OsipsSessionManager) RemoveSession(uuid string) {
+ return
+}
+func (osm *OsipsSessionManager) MaxDebit(cd *engine.CallDescriptor, cc *engine.CallCost) error {
+ return nil
+}
+func (osm *OsipsSessionManager) GetDebitPeriod() time.Duration {
+ var nilDuration time.Duration
+ return nilDuration
+}
+func (osm *OsipsSessionManager) GetDbLogger() engine.LogStorage {
+ return nil
+}
+func (osm *OsipsSessionManager) Shutdown() error {
+ return nil
+}
+
+// Event Handlers
+
+// Automatic subscribe to OpenSIPS for events, trigered on Connect or OpenSIPS restart
+func (osm *OsipsSessionManager) SubscribeEvents(evStop chan struct{}) error {
+ for {
+ select {
+ case <-evStop: // Break this loop from outside
+ return nil
+ default:
+ subscribeInterval := osm.cgrCfg.OsipsEvSubscInterval + time.Duration(1)*time.Second // Avoid concurrency on expiry
+ listenAddrSplt := strings.Split(osm.cgrCfg.OsipsListenUdp, ":")
+ portListen := listenAddrSplt[1]
+ addrListen := listenAddrSplt[0]
+ if len(addrListen) == 0 { //Listen on all addresses, try finding out from mi connection
+ if localAddr := osm.miConn.LocallAddr(); localAddr != nil {
+ addrListen = strings.Split(localAddr.String(), ":")[1]
+ }
+ }
+ for eventName := range osm.eventHandlers {
+ if eventName == "E_OPENSIPS_START" { // Do not subscribe for start since this should be hardcoded
+ continue
+ }
+ cmd := fmt.Sprintf(":event_subscribe:\n%s\nudp:%s:%s\n%d\n", eventName, addrListen, portListen, int(subscribeInterval.Seconds()))
+ success := false
+ for attempts := 0; attempts < osm.cgrCfg.OsipsReconnects; attempts++ {
+ if reply, err := osm.miConn.SendCommand([]byte(cmd)); err == nil && bytes.HasPrefix(reply, []byte("200 OK")) {
+ success = true
+ break
+ }
+ time.Sleep(time.Duration((attempts+1)/2) * time.Second) // Allow OpenSIPS to recover from errors
+ continue // Try again
+ }
+ if !success {
+ close(osm.stopServing) // Do not serve anymore since we got errors on subscribing
+ return errors.New("Failed subscribing to OpenSIPS events")
+ }
+ }
+ time.Sleep(osm.cgrCfg.OsipsEvSubscInterval)
+ }
+ }
+ return nil
+}
+
+func (osm *OsipsSessionManager) OnOpensipsStart(cdrDagram *osipsdagram.OsipsEvent) {
+ close(*osm.evSubscribeStop) // Cancel previous subscribes
+ evStop := make(chan struct{})
+ osm.evSubscribeStop = &evStop
+ go osm.SubscribeEvents(evStop)
+}
+
+func (osm *OsipsSessionManager) OnCdr(cdrDagram *osipsdagram.OsipsEvent) {
+ var reply string
+ osipsEv, _ := NewOsipsEvent(cdrDagram)
+ storedCdr := osipsEv.AsStoredCdr()
+ if err := osm.cdrsrv.ProcessCdr(storedCdr, &reply); err != nil {
+ engine.Logger.Err(fmt.Sprintf(" Failed processing CDR, cgrid: %s, accid: %s, error: <%s>", storedCdr.CgrId, storedCdr.AccId, err.Error()))
+ }
+}
diff --git a/sessionmanager/session.go b/sessionmanager/session.go
index 792902d56..c80ceff4b 100644
--- a/sessionmanager/session.go
+++ b/sessionmanager/session.go
@@ -25,6 +25,7 @@ import (
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
+ "github.com/cgrates/fsock"
)
// Session type holding the call information fields, a session delegate for specific
@@ -110,13 +111,18 @@ func (s *Session) debitLoop(runIdx int) {
cc := new(engine.CallCost)
if err := s.sessionManager.MaxDebit(&nextCd, cc); err != nil {
engine.Logger.Err(fmt.Sprintf("Could not complete debit opperation: %v", err))
- s.sessionManager.DisconnectSession(s.uuid, SYSTEM_ERROR)
+ s.sessionManager.DisconnectSession(s.uuid, SYSTEM_ERROR, "")
return
}
if cc.GetDuration() == 0 {
- s.sessionManager.DisconnectSession(s.uuid, INSUFFICIENT_FUNDS)
+ s.sessionManager.DisconnectSession(s.uuid, INSUFFICIENT_FUNDS, nextCd.Destination)
return
}
+ if cc.GetDuration() <= cfg.FSMinDurLowBalance && len(cfg.FSLowBalanceAnnFile) != 0 {
+ if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_broadcast %s %s aleg\n\n", s.uuid, cfg.FSLowBalanceAnnFile)); err != nil {
+ engine.Logger.Err(fmt.Sprintf(" Could not send uuid_broadcast to freeswitch: %s", err.Error()))
+ }
+ }
s.sessionRuns[runIdx].callCosts = append(s.sessionRuns[runIdx].callCosts, cc)
nextCd.TimeEnd = cc.GetEndTime() // set debited timeEnd
// update call duration with real debited duration
diff --git a/sessionmanager/sessionmanager.go b/sessionmanager/sessionmanager.go
index 2367c0a6b..c05dab8d2 100644
--- a/sessionmanager/sessionmanager.go
+++ b/sessionmanager/sessionmanager.go
@@ -1,6 +1,6 @@
/*
Real-time Charging System for Telecom & ISP environments
-Copyright (C) 2012-2014 ITsysCOM GmbH
+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
@@ -21,13 +21,12 @@ package sessionmanager
import (
"time"
- "github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
)
type SessionManager interface {
- Connect(*config.CGRConfig) error
- DisconnectSession(string, string)
+ Connect() error
+ DisconnectSession(string, string, string)
RemoveSession(string)
MaxDebit(*engine.CallDescriptor, *engine.CallCost) error
GetDebitPeriod() time.Duration
diff --git a/test.sh b/test.sh
index 984caa3ae..5497f0aaf 100755
--- a/test.sh
+++ b/test.sh
@@ -1,13 +1,11 @@
-#! /usr/bin/env sh
+1#! /usr/bin/env sh
go test -i github.com/cgrates/cgrates/engine
go test -i github.com/cgrates/cgrates/sessionmanager
go test -i github.com/cgrates/cgrates/config
go test -i github.com/cgrates/cgrates/cmd/cgr-engine
-go test -i github.com/cgrates/cgrates/mediator
go test -i github.com/cgrates/fsock
go test -i github.com/cgrates/cgrates/cache2go
-go test -i github.com/cgrates/cgrates/cdrs
go test -i github.com/cgrates/cgrates/cdrc
go test -i github.com/cgrates/cgrates/utils
go test -i github.com/cgrates/cgrates/history
@@ -23,10 +21,6 @@ go test github.com/cgrates/cgrates/config
cfg=$?
go test github.com/cgrates/cgrates/cmd/cgr-engine
cr=$?
-go test github.com/cgrates/cgrates/mediator
-md=$?
-go test github.com/cgrates/cgrates/cdrs
-cdrs=$?
go test github.com/cgrates/cgrates/cdrc
cdrcs=$?
go test github.com/cgrates/cgrates/utils
@@ -40,4 +34,5 @@ c2g=$?
go test github.com/cgrates/cgrates/cdre
cdre=$?
-exit $en && $gt && $sm && $cfg && $bl && $cr && $md && $cdrs && $cdrc && $fs && $ut && $hs && $c2g && $cdre
+exit $en && $gt && $sm && $cfg && $bl && $cr && $cdrc && $fs && $ut && $hs && $c2g && $cdre
+
diff --git a/update_external_libs.sh b/update_external_libs.sh
index 5d9939702..605b8194c 100755
--- a/update_external_libs.sh
+++ b/update_external_libs.sh
@@ -4,6 +4,7 @@ go get -v -u github.com/bmizerany/pq
go get -v -u github.com/ugorji/go/codec
go get -v -u labix.org/v2/mgo
go get -v -u github.com/cgrates/fsock
+go get -v -u github.com/cgrates/osipsdagram
go get -u -v github.com/go-sql-driver/mysql
go get -u -v github.com/hoisie/redis
go get -u -v github.com/howeyc/fsnotify
diff --git a/utils/apitpdata.go b/utils/apitpdata.go
index 5be838c35..3686af663 100644
--- a/utils/apitpdata.go
+++ b/utils/apitpdata.go
@@ -340,23 +340,26 @@ type AttrExpFileCdrs struct {
MaskDestinationId *string // Overwrite configured MaskDestId
MaskLength *int // Overwrite configured MaskLength, -1 to use general config ones
CgrIds []string // If provided, it will filter based on the cgrids present in list
- MediationRunId []string // If provided, it will filter on mediation runid
- TOR []string // If provided, filter on TypeOfRecord
- CdrHost []string // If provided, it will filter cdrhost
- CdrSource []string // If provided, it will filter cdrsource
- ReqType []string // If provided, it will fiter reqtype
- Direction []string // If provided, it will fiter direction
- Tenant []string // If provided, it will filter tenant
- Category []string // If provided, it will filter çategory
- Account []string // If provided, it will filter account
- Subject []string // If provided, it will filter the rating subject
- DestinationPrefix []string // If provided, it will filter on destination prefix
+ MediationRunIds []string // If provided, it will filter on mediation runid
+ TORs []string // If provided, filter on TypeOfRecord
+ CdrHosts []string // If provided, it will filter cdrhost
+ CdrSources []string // If provided, it will filter cdrsource
+ ReqTypes []string // If provided, it will fiter reqtype
+ Directions []string // If provided, it will fiter direction
+ Tenants []string // If provided, it will filter tenant
+ Categories []string // If provided, it will filter çategory
+ Accounts []string // If provided, it will filter account
+ Subjects []string // If provided, it will filter the rating subject
+ DestinationPrefixes []string // If provided, it will filter on destination prefix
+ RatedAccounts []string // If provided, it will filter ratedaccount
+ RatedSubjects []string // If provided, it will filter the ratedsubject
OrderIdStart int64 // Export from this order identifier
OrderIdEnd int64 // Export smaller than this order identifier
TimeStart string // If provided, it will represent the starting of the CDRs interval (>=)
TimeEnd string // If provided, it will represent the end of the CDRs interval (<)
SkipErrors bool // Do not export errored CDRs
SkipRated bool // Do not export rated CDRs
+ SuppressCgrIds bool // Disable CgrIds reporting in reply/ExportedCgrIds and reply/UnexportedCgrIds
}
type ExportedFileCdrs struct {
@@ -370,24 +373,26 @@ type ExportedFileCdrs struct {
}
type AttrGetCdrs struct {
- CgrIds []string // If provided, it will filter based on the cgrids present in list
- MediationRunId []string // If provided, it will filter on mediation runid
- TOR []string // If provided, filter on TypeOfRecord
- CdrHost []string // If provided, it will filter cdrhost
- CdrSource []string // If provided, it will filter cdrsource
- ReqType []string // If provided, it will fiter reqtype
- Direction []string // If provided, it will fiter direction
- Tenant []string // If provided, it will filter tenant
- Category []string // If provided, it will filter çategory
- Account []string // If provided, it will filter account
- Subject []string // If provided, it will filter the rating subject
- DestinationPrefix []string // If provided, it will filter on destination prefix
- OrderIdStart int64 // Export from this order identifier
- OrderIdEnd int64 // Export smaller than this order identifier
- TimeStart string // If provided, it will represent the starting of the CDRs interval (>=)
- TimeEnd string // If provided, it will represent the end of the CDRs interval (<)
- SkipErrors bool // Do not export errored CDRs
- SkipRated bool // Do not export rated CDRs
+ CgrIds []string // If provided, it will filter based on the cgrids present in list
+ MediationRunIds []string // If provided, it will filter on mediation runid
+ TORs []string // If provided, filter on TypeOfRecord
+ CdrHosts []string // If provided, it will filter cdrhost
+ CdrSources []string // If provided, it will filter cdrsource
+ ReqTypes []string // If provided, it will fiter reqtype
+ Directions []string // If provided, it will fiter direction
+ Tenants []string // If provided, it will filter tenant
+ Categories []string // If provided, it will filter çategory
+ Accounts []string // If provided, it will filter account
+ Subjects []string // If provided, it will filter the rating subject
+ DestinationPrefixes []string // If provided, it will filter on destination prefix
+ RatedAccounts []string // If provided, it will filter ratedaccount
+ RatedSubjects []string // If provided, it will filter the ratedsubject
+ OrderIdStart int64 // Export from this order identifier
+ OrderIdEnd int64 // Export smaller than this order identifier
+ TimeStart string // If provided, it will represent the starting of the CDRs interval (>=)
+ TimeEnd string // If provided, it will represent the end of the CDRs interval (<)
+ SkipErrors bool // Do not export errored CDRs
+ SkipRated bool // Do not export rated CDRs
}
type AttrRemCdrs struct {
@@ -395,11 +400,27 @@ type AttrRemCdrs struct {
}
type AttrRateCdrs struct {
- TimeStart string // Cdrs time start
- TimeEnd string // Cdrs time end
- RerateErrors bool // Rerate previous CDRs with errors (makes sense for reqtype rated and pseudoprepaid
- RerateRated bool // Rerate CDRs which were previously rated (makes sense for reqtype rated and pseudoprepaid)
- SendToStats bool // Set to true if the CDRs should be sent to stats server
+ CgrIds []string // If provided, it will filter based on the cgrids present in list
+ MediationRunIds []string // If provided, it will filter on mediation runid
+ TORs []string // If provided, filter on TypeOfRecord
+ CdrHosts []string // If provided, it will filter cdrhost
+ CdrSources []string // If provided, it will filter cdrsource
+ ReqTypes []string // If provided, it will fiter reqtype
+ Directions []string // If provided, it will fiter direction
+ Tenants []string // If provided, it will filter tenant
+ Categories []string // If provided, it will filter çategory
+ Accounts []string // If provided, it will filter account
+ Subjects []string // If provided, it will filter the rating subject
+ DestinationPrefixes []string // If provided, it will filter on destination prefix
+ RatedAccounts []string // If provided, it will filter ratedaccount
+ RatedSubjects []string // If provided, it will filter the ratedsubject
+ OrderIdStart int64 // Export from this order identifier
+ OrderIdEnd int64 // Export smaller than this order identifier
+ TimeStart string // If provided, it will represent the starting of the CDRs interval (>=)
+ TimeEnd string // If provided, it will represent the end of the CDRs interval (<)
+ RerateErrors bool // Rerate previous CDRs with errors (makes sense for reqtype rated and pseudoprepaid
+ RerateRated bool // Rerate CDRs which were previously rated (makes sense for reqtype rated and pseudoprepaid)
+ SendToStats bool // Set to true if the CDRs should be sent to stats server
}
type AttrLoadTpFromFolder struct {
diff --git a/utils/consts.go b/utils/consts.go
index d5a6424df..7e385f7b2 100644
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -1,134 +1,161 @@
package utils
const (
- VERSION = "0.9.1rc5"
- POSTGRES = "postgres"
- MYSQL = "mysql"
- MONGO = "mongo"
- REDIS = "redis"
- LOCALHOST = "127.0.0.1"
- FSCDR_FILE_CSV = "freeswitch_file_csv"
- FSCDR_HTTP_JSON = "freeswitch_http_json"
- NOT_IMPLEMENTED = "not implemented"
- PREPAID = "prepaid"
- POSTPAID = "postpaid"
- PSEUDOPREPAID = "pseudoprepaid"
- RATED = "rated"
- ERR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
- ERR_SERVER_ERROR = "SERVER_ERROR"
- ERR_NOT_FOUND = "NOT_FOUND"
- ERR_MANDATORY_IE_MISSING = "MANDATORY_IE_MISSING"
- ERR_EXISTS = "EXISTS"
- ERR_BROKEN_REFERENCE = "BROKEN_REFERENCE"
- ERR_PARSER_ERROR = "PARSER_ERROR"
- TBL_TP_TIMINGS = "tp_timings"
- TBL_TP_DESTINATIONS = "tp_destinations"
- TBL_TP_RATES = "tp_rates"
- TBL_TP_DESTINATION_RATES = "tp_destination_rates"
- TBL_TP_RATING_PLANS = "tp_rating_plans"
- TBL_TP_RATE_PROFILES = "tp_rating_profiles"
- TBL_TP_SHARED_GROUPS = "tp_shared_groups"
- TBL_TP_LCRS = "tp_lcr_rules"
- TBL_TP_ACTIONS = "tp_actions"
- TBL_TP_ACTION_PLANS = "tp_action_plans"
- TBL_TP_ACTION_TRIGGERS = "tp_action_triggers"
- TBL_TP_ACCOUNT_ACTIONS = "tp_account_actions"
- TBL_CDRS_PRIMARY = "cdrs_primary"
- TBL_CDRS_EXTRA = "cdrs_extra"
- TBL_COST_DETAILS = "cost_details"
- TBL_RATED_CDRS = "rated_cdrs"
- TIMINGS_CSV = "Timings.csv"
- DESTINATIONS_CSV = "Destinations.csv"
- RATES_CSV = "Rates.csv"
- DESTINATION_RATES_CSV = "DestinationRates.csv"
- RATING_PLANS_CSV = "RatingPlans.csv"
- RATING_PROFILES_CSV = "RatingProfiles.csv"
- SHARED_GROUPS_CSV = "SharedGroups.csv"
- LCRS_CSV = "LCRRules.csv"
- ACTIONS_CSV = "Actions.csv"
- ACTION_PLANS_CSV = "ActionPlans.csv"
- ACTION_TRIGGERS_CSV = "ActionTriggers.csv"
- ACCOUNT_ACTIONS_CSV = "AccountActions.csv"
- DERIVED_CHARGERS_CSV = "DerivedChargers.csv"
- CDR_STATS_CSV = "CdrStats.csv"
- TIMINGS_NRCOLS = 6
- DESTINATIONS_NRCOLS = 2
- RATES_NRCOLS = 6
- DESTINATION_RATES_NRCOLS = 5
- DESTRATE_TIMINGS_NRCOLS = 4
- RATE_PROFILES_NRCOLS = 7
- SHARED_GROUPS_NRCOLS = 4
- LCRS_NRCOLS = 9
- ACTIONS_NRCOLS = 12
- ACTION_PLANS_NRCOLS = 4
- ACTION_TRIGGERS_NRCOLS = 15
- ACCOUNT_ACTIONS_NRCOLS = 5
- DERIVED_CHARGERS_NRCOLS = 17
- CDR_STATS_NRCOLS = 19
- ROUNDING_UP = "*up"
- ROUNDING_MIDDLE = "*middle"
- ROUNDING_DOWN = "*down"
- ANY = "*any"
- COMMENT_CHAR = '#'
- CSV_SEP = ','
- FALLBACK_SEP = ';'
- INFIELD_SEP = ";"
- FIELDS_SEP = ","
- REGEXP_PREFIX = "~"
- JSON = "json"
- GOB = "gob"
- MSGPACK = "msgpack"
- CSV_LOAD = "CSVLOAD"
- CGRID = "cgrid"
- ORDERID = "orderid"
- ACCID = "accid"
- CDRHOST = "cdrhost"
- CDRSOURCE = "cdrsource"
- REQTYPE = "reqtype"
- DIRECTION = "direction"
- TENANT = "tenant"
- CATEGORY = "category"
- ACCOUNT = "account"
- SUBJECT = "subject"
- DESTINATION = "destination"
- SETUP_TIME = "setup_time"
- ANSWER_TIME = "answer_time"
- USAGE = "usage"
- MEDI_RUNID = "mediation_runid"
- COST = "cost"
- DEFAULT_RUNID = "default"
- STATIC_VALUE_PREFIX = "^"
- CSV = "csv"
- CDRE_DRYRUN = "dry_run"
- INTERNAL = "internal"
- ZERO_RATING_SUBJECT_PREFIX = "*zero"
- OK = "OK"
- CDRE_FIXED_WIDTH = "fwv"
- XML_PROFILE_PREFIX = "*xml:"
- CDRE = "cdre"
- CDRC = "cdrc"
- MASK_CHAR = "*"
- CONCATENATED_KEY_SEP = ":"
- META_DEFAULT = "*default"
- FORKED_CDR = "forked_cdr"
- UNIT_TEST = "UNIT_TEST"
- HDR_VAL_SEP = "/"
- MONETARY = "*monetary"
- SMS = "*sms"
- DATA = "*data"
- VOICE = "*voice"
- TOR = "tor"
- HOURS = "hours"
- MINUTES = "minutes"
- NANOSECONDS = "nanoseconds"
- SECONDS = "seconds"
- OUT = "*out"
- CDR_IMPORT = "cdr_import"
- CDR_EXPORT = "cdr_export"
- CDRFIELD = "cdrfield"
- ASR = "ASR"
- ACD = "ACD"
- FILTER_REGEXP_TPL = "$1$2$3$4$5"
+ VERSION = "0.9.1rc5"
+ POSTGRES = "postgres"
+ MYSQL = "mysql"
+ MONGO = "mongo"
+ REDIS = "redis"
+ LOCALHOST = "127.0.0.1"
+ FSCDR_FILE_CSV = "freeswitch_file_csv"
+ FSCDR_HTTP_JSON = "freeswitch_http_json"
+ NOT_IMPLEMENTED = "not implemented"
+ PREPAID = "prepaid"
+ POSTPAID = "postpaid"
+ PSEUDOPREPAID = "pseudoprepaid"
+ RATED = "rated"
+ ERR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
+ ERR_SERVER_ERROR = "SERVER_ERROR"
+ ERR_NOT_FOUND = "NOT_FOUND"
+ ERR_MANDATORY_IE_MISSING = "MANDATORY_IE_MISSING"
+ ERR_EXISTS = "EXISTS"
+ ERR_BROKEN_REFERENCE = "BROKEN_REFERENCE"
+ ERR_PARSER_ERROR = "PARSER_ERROR"
+ TBL_TP_TIMINGS = "tp_timings"
+ TBL_TP_DESTINATIONS = "tp_destinations"
+ TBL_TP_RATES = "tp_rates"
+ TBL_TP_DESTINATION_RATES = "tp_destination_rates"
+ TBL_TP_RATING_PLANS = "tp_rating_plans"
+ TBL_TP_RATE_PROFILES = "tp_rating_profiles"
+ TBL_TP_SHARED_GROUPS = "tp_shared_groups"
+ TBL_TP_LCRS = "tp_lcr_rules"
+ TBL_TP_ACTIONS = "tp_actions"
+ TBL_TP_ACTION_PLANS = "tp_action_plans"
+ TBL_TP_ACTION_TRIGGERS = "tp_action_triggers"
+ TBL_TP_ACCOUNT_ACTIONS = "tp_account_actions"
+ TBL_CDRS_PRIMARY = "cdrs_primary"
+ TBL_CDRS_EXTRA = "cdrs_extra"
+ TBL_COST_DETAILS = "cost_details"
+ TBL_RATED_CDRS = "rated_cdrs"
+ TIMINGS_CSV = "Timings.csv"
+ DESTINATIONS_CSV = "Destinations.csv"
+ RATES_CSV = "Rates.csv"
+ DESTINATION_RATES_CSV = "DestinationRates.csv"
+ RATING_PLANS_CSV = "RatingPlans.csv"
+ RATING_PROFILES_CSV = "RatingProfiles.csv"
+ SHARED_GROUPS_CSV = "SharedGroups.csv"
+ LCRS_CSV = "LCRRules.csv"
+ ACTIONS_CSV = "Actions.csv"
+ ACTION_PLANS_CSV = "ActionPlans.csv"
+ ACTION_TRIGGERS_CSV = "ActionTriggers.csv"
+ ACCOUNT_ACTIONS_CSV = "AccountActions.csv"
+ DERIVED_CHARGERS_CSV = "DerivedChargers.csv"
+ CDR_STATS_CSV = "CdrStats.csv"
+ TIMINGS_NRCOLS = 6
+ DESTINATIONS_NRCOLS = 2
+ RATES_NRCOLS = 6
+ DESTINATION_RATES_NRCOLS = 5
+ DESTRATE_TIMINGS_NRCOLS = 4
+ RATE_PROFILES_NRCOLS = 7
+ SHARED_GROUPS_NRCOLS = 4
+ LCRS_NRCOLS = 9
+ ACTIONS_NRCOLS = 12
+ ACTION_PLANS_NRCOLS = 4
+ ACTION_TRIGGERS_NRCOLS = 15
+ ACCOUNT_ACTIONS_NRCOLS = 5
+ DERIVED_CHARGERS_NRCOLS = 17
+ CDR_STATS_NRCOLS = 19
+ ROUNDING_UP = "*up"
+ ROUNDING_MIDDLE = "*middle"
+ ROUNDING_DOWN = "*down"
+ ANY = "*any"
+ COMMENT_CHAR = '#'
+ CSV_SEP = ','
+ FALLBACK_SEP = ';'
+ INFIELD_SEP = ";"
+ FIELDS_SEP = ","
+ REGEXP_PREFIX = "~"
+ JSON = "json"
+ GOB = "gob"
+ MSGPACK = "msgpack"
+ CSV_LOAD = "CSVLOAD"
+ CGRID = "cgrid"
+ ORDERID = "orderid"
+ ACCID = "accid"
+ CDRHOST = "cdrhost"
+ CDRSOURCE = "cdrsource"
+ REQTYPE = "reqtype"
+ DIRECTION = "direction"
+ TENANT = "tenant"
+ CATEGORY = "category"
+ ACCOUNT = "account"
+ SUBJECT = "subject"
+ DESTINATION = "destination"
+ SETUP_TIME = "setup_time"
+ ANSWER_TIME = "answer_time"
+ USAGE = "usage"
+ MEDI_RUNID = "mediation_runid"
+ RATED_ACCOUNT = "rated_account"
+ RATED_SUBJECT = "rated_subject"
+ COST = "cost"
+ DEFAULT_RUNID = "default"
+ STATIC_VALUE_PREFIX = "^"
+ CSV = "csv"
+ CDRE_DRYRUN = "dry_run"
+ INTERNAL = "internal"
+ ZERO_RATING_SUBJECT_PREFIX = "*zero"
+ OK = "OK"
+ CDRE_FIXED_WIDTH = "fwv"
+ XML_PROFILE_PREFIX = "*xml:"
+ CDRE = "cdre"
+ CDRC = "cdrc"
+ MASK_CHAR = "*"
+ CONCATENATED_KEY_SEP = ":"
+ META_DEFAULT = "*default"
+ FORKED_CDR = "forked_cdr"
+ UNIT_TEST = "UNIT_TEST"
+ HDR_VAL_SEP = "/"
+ MONETARY = "*monetary"
+ SMS = "*sms"
+ DATA = "*data"
+ VOICE = "*voice"
+ TOR = "tor"
+ HOURS = "hours"
+ MINUTES = "minutes"
+ NANOSECONDS = "nanoseconds"
+ SECONDS = "seconds"
+ OUT = "*out"
+ CDR_IMPORT = "cdr_import"
+ CDR_EXPORT = "cdr_export"
+ CDRFIELD = "cdrfield"
+ ASR = "ASR"
+ ACD = "ACD"
+ FILTER_REGEXP_TPL = "$1$2$3$4$5"
+ ACTION_TIMING_PREFIX = "apl_"
+ RATING_PLAN_PREFIX = "rpl_"
+ RATING_PROFILE_PREFIX = "rpf_"
+ RP_ALIAS_PREFIX = "ral_"
+ ACC_ALIAS_PREFIX = "aal_"
+ ACTION_PREFIX = "act_"
+ SHARED_GROUP_PREFIX = "shg_"
+ ACCOUNT_PREFIX = "ubl_"
+ DESTINATION_PREFIX = "dst_"
+ LCR_PREFIX = "lcr_"
+ DERIVEDCHARGERS_PREFIX = "dcs_"
+ TEMP_DESTINATION_PREFIX = "tmp_"
+ LOG_CALL_COST_PREFIX = "cco_"
+ LOG_ACTION_TIMMING_PREFIX = "ltm_"
+ LOG_ACTION_TRIGGER_PREFIX = "ltr_"
+ LOG_ERR = "ler_"
+ LOG_CDR = "cdr_"
+ LOG_MEDIATED_CDR = "mcd_"
+ SESSION_MANAGER_SOURCE = "SMR"
+ MEDIATOR_SOURCE = "MED"
+ SCHED_SOURCE = "SCH"
+ RATER_SOURCE = "RAT"
+ CREATE_CDRS_TABLES_SQL = "create_cdrs_tables.sql"
+ CREATE_TARIFFPLAN_TABLES_SQL = "create_tariffplan_tables.sql"
+ TEST_SQL = "TEST_SQL"
)
var (
diff --git a/utils/storedcdr.go b/utils/storedcdr.go
index 518a2a124..74e43dc68 100644
--- a/utils/storedcdr.go
+++ b/utils/storedcdr.go
@@ -47,6 +47,8 @@ type StoredCdr struct {
Usage time.Duration
ExtraFields map[string]string
MediationRunId string
+ RatedAccount string // Populated out of rating data
+ RatedSubject string
Cost float64
}
@@ -126,6 +128,10 @@ func (storedCdr *StoredCdr) FieldAsString(rsrFld *RSRField) string {
return rsrFld.ParseValue(strconv.FormatInt(storedCdr.Usage.Nanoseconds(), 10))
case MEDI_RUNID:
return rsrFld.ParseValue(storedCdr.MediationRunId)
+ case RATED_ACCOUNT:
+ return rsrFld.ParseValue(storedCdr.RatedAccount)
+ case RATED_SUBJECT:
+ return rsrFld.ParseValue(storedCdr.RatedSubject)
case COST:
return rsrFld.ParseValue(strconv.FormatFloat(storedCdr.Cost, 'f', -1, 64)) // Recommended to use FormatCost
default:
@@ -157,7 +163,7 @@ func (storedCdr *StoredCdr) AsStoredCdr() *StoredCdr {
return storedCdr
}
-// Ability to send the CgrCdr remotely to another CDR server
+// Ability to send the CgrCdr remotely to another CDR server, we do not include rating variables for now
func (storedCdr *StoredCdr) AsHttpForm() url.Values {
v := url.Values{}
for fld, val := range storedCdr.ExtraFields {
@@ -324,6 +330,8 @@ func (storedCdr *StoredCdr) AsCgrCdrOut() *CgrCdrOut {
Usage: storedCdr.Usage.Seconds(),
ExtraFields: storedCdr.ExtraFields,
MediationRunId: storedCdr.MediationRunId,
+ RatedAccount: storedCdr.RatedAccount,
+ RatedSubject: storedCdr.RatedSubject,
Cost: storedCdr.Cost,
}
}
@@ -347,5 +355,7 @@ type CgrCdrOut struct {
Usage float64
ExtraFields map[string]string
MediationRunId string
+ RatedAccount string
+ RatedSubject string
Cost float64
}
diff --git a/utils/storedcdr_test.go b/utils/storedcdr_test.go
index 62a3bf97c..8e33a591e 100644
--- a/utils/storedcdr_test.go
+++ b/utils/storedcdr_test.go
@@ -32,7 +32,7 @@ func TestStoredCdrInterfaces(t *testing.T) {
func TestFieldAsString(t *testing.T) {
cdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
- Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
+ Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans",
}
if cdr.FieldAsString(&RSRField{Id: CGRID}) != cdr.CgrId ||
cdr.FieldAsString(&RSRField{Id: ORDERID}) != "123" ||
@@ -51,6 +51,8 @@ func TestFieldAsString(t *testing.T) {
cdr.FieldAsString(&RSRField{Id: USAGE}) != "10000000000" ||
cdr.FieldAsString(&RSRField{Id: MEDI_RUNID}) != cdr.MediationRunId ||
cdr.FieldAsString(&RSRField{Id: COST}) != "1.01" ||
+ cdr.FieldAsString(&RSRField{Id: RATED_ACCOUNT}) != "dan" ||
+ cdr.FieldAsString(&RSRField{Id: RATED_SUBJECT}) != "dans" ||
cdr.FieldAsString(&RSRField{Id: "field_extr1"}) != cdr.ExtraFields["field_extr1"] ||
cdr.FieldAsString(&RSRField{Id: "fieldextr2"}) != cdr.ExtraFields["fieldextr2"] ||
cdr.FieldAsString(&RSRField{Id: "dummy_field"}) != "" {
@@ -71,6 +73,8 @@ func TestFieldAsString(t *testing.T) {
cdr.FieldAsString(&RSRField{Id: ANSWER_TIME}) != cdr.AnswerTime.String(),
cdr.FieldAsString(&RSRField{Id: USAGE}) != "10000000000",
cdr.FieldAsString(&RSRField{Id: MEDI_RUNID}) != cdr.MediationRunId,
+ cdr.FieldAsString(&RSRField{Id: RATED_ACCOUNT}) != "dan",
+ cdr.FieldAsString(&RSRField{Id: RATED_SUBJECT}) != "dans",
cdr.FieldAsString(&RSRField{Id: COST}) != "1.01",
cdr.FieldAsString(&RSRField{Id: "field_extr1"}) != cdr.ExtraFields["field_extr1"],
cdr.FieldAsString(&RSRField{Id: "fieldextr2"}) != cdr.ExtraFields["fieldextr2"],
@@ -234,7 +238,7 @@ func TestStoredCdrAsHttpForm(t *testing.T) {
storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
- Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
+ Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, RatedSubject: "dans", Cost: 1.01,
}
cdrForm := storCdr.AsHttpForm()
if cdrForm.Get(TOR) != VOICE {
@@ -291,7 +295,7 @@ func TestStoredCdrForkCdr(t *testing.T) {
storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
- Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "field_extr2": "valextr2"}, Cost: 1.01,
+ Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "field_extr2": "valextr2"}, Cost: 1.01, RatedSubject: "dans",
}
rtSampleCdrOut, err := storCdr.ForkCdr("sample_run1", &RSRField{Id: REQTYPE}, &RSRField{Id: DIRECTION}, &RSRField{Id: TENANT}, &RSRField{Id: CATEGORY},
&RSRField{Id: ACCOUNT}, &RSRField{Id: SUBJECT}, &RSRField{Id: DESTINATION}, &RSRField{Id: SETUP_TIME}, &RSRField{Id: ANSWER_TIME}, &RSRField{Id: USAGE},
@@ -379,12 +383,12 @@ func TestStoredCdrAsCgrCdrOut(t *testing.T) {
storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
- Usage: time.Duration(10), ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
+ Usage: time.Duration(10), ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans",
}
expectOutCdr := &CgrCdrOut{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
- Usage: 0.00000001, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
+ Usage: 0.00000001, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans",
}
if cdrOut := storCdr.AsCgrCdrOut(); !reflect.DeepEqual(expectOutCdr, cdrOut) {
t.Errorf("Expected: %+v, received: %+v", expectOutCdr, cdrOut)