From 5bc5509ea36d6c1d89a2dcad5f0f3456ea1d40ac Mon Sep 17 00:00:00 2001 From: DanB Date: Wed, 25 Nov 2015 16:23:13 +0100 Subject: [PATCH 01/30] RedisNotFound error for GetAccount --- agents/libdmt.go | 21 +++++++++++++++++++++ engine/storage_redis.go | 29 ++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/agents/libdmt.go b/agents/libdmt.go index e290e0cc0..81ee70fda 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -46,6 +46,11 @@ func init() { rand.Seed(time.Now().UnixNano()) } +const ( + META_CCR_USAGE = "*ccr_usage" + META_CCR_SMG_EVENT_NAME = "*ccr_smg_event_name" +) + func loadDictionaries(dictsDir, componentId string) error { fi, err := os.Stat(dictsDir) if err != nil { @@ -329,6 +334,11 @@ func avpValAsString(a *diam.AVP) string { // Extracts data out of CCR into a SMGenericEvent based on the configured template func (self *CCR) AsSMGenericEvent(tpl []*config.CfgCdrField) (sessionmanager.SMGenericEvent, error) { outMap := make(map[string]string) // work with it so we can append values to keys + if evName, err := self.metaHandler(META_CCR_SMG_EVENT_NAME, ""); err != nil { + return nil, err + } else { + outMap[utils.EVENT_NAME] = evName + } for _, fldTpl := range tpl { var outVal string for _, rsrTpl := range fldTpl.Value { @@ -361,6 +371,17 @@ func (self *CCR) AsSMGenericEvent(tpl []*config.CfgCdrField) (sessionmanager.SMG return sessionmanager.SMGenericEvent(utils.ConvertMapValStrIf(outMap)), nil } +// Handler for meta functions +func (self *CCR) metaHandler(tag, arg string) (string, error) { + switch tag { + case META_CCR_SMG_EVENT_NAME: + return "", nil + case META_CCR_USAGE: + return "", nil + } + return "", nil +} + func NewCCAFromCCR(ccr *CCR) *CCA { return &CCA{SessionId: ccr.SessionId, AuthApplicationId: ccr.AuthApplicationId, CCRequestType: ccr.CCRequestType, CCRequestNumber: ccr.CCRequestNumber, diamMessage: diam.NewMessage(ccr.diamMessage.Header.CommandCode, ccr.diamMessage.Header.CommandFlags&^diam.RequestFlag, ccr.diamMessage.Header.ApplicationID, diff --git a/engine/storage_redis.go b/engine/storage_redis.go index fe05e1c86..d93a59d14 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -23,14 +23,17 @@ import ( "compress/zlib" "errors" "fmt" + "io/ioutil" + "time" "github.com/cgrates/cgrates/cache2go" "github.com/cgrates/cgrates/utils" "github.com/mediocregopher/radix.v2/pool" "github.com/mediocregopher/radix.v2/redis" +) - "io/ioutil" - "time" +var ( + ErrRedisNotFound = errors.New("RedisNotFound") ) type RedisStorage struct { @@ -607,14 +610,22 @@ func (rs *RedisStorage) SetSharedGroup(sg *SharedGroup) (err error) { return } -func (rs *RedisStorage) GetAccount(key string) (ub *Account, err error) { - var values []byte - if values, err = rs.db.Cmd("GET", utils.ACCOUNT_PREFIX+key).Bytes(); err == nil { - ub = &Account{Id: key} - err = rs.ms.Unmarshal(values, ub) +func (rs *RedisStorage) GetAccount(key string) (*Account, error) { + rpl := rs.db.Cmd("GET", utils.ACCOUNT_PREFIX+key) + if rpl.Err != nil { + return nil, rpl.Err + } else if rpl.IsType(redis.Nil) { + return nil, ErrRedisNotFound } - - return + values, err := rpl.Bytes() + if err != nil { + return nil, err + } + ub := &Account{Id: key} + if err = rs.ms.Unmarshal(values, ub); err != nil { + return nil, err + } + return ub, nil } func (rs *RedisStorage) SetAccount(ub *Account) (err error) { From 1d8194dff1c7f128afad71cfc6781a61cd3623fc Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 25 Nov 2015 19:50:18 +0200 Subject: [PATCH 02/30] better error and logging from engine --- engine/calldesc.go | 18 +++++++++--------- utils/consts.go | 1 + 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index a83cb997d..399e96442 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -196,7 +196,7 @@ func (cd *CallDescriptor) LoadRatingPlans() (err error) { } //load the rating plans if err != nil || !cd.continousRatingInfos() { - //log.Print("ERR: ", cd.GetKey(cd.Subject), err) + utils.Logger.Err(fmt.Sprintf("Destination %s not authorized for account: %s, subject: %s", cd.Destination, cd.GetAccountKey(), cd.GetKey(cd.Subject))) err = utils.ErrUnauthorizedDestination } return @@ -496,7 +496,7 @@ func (cd *CallDescriptor) getCost() (*CallCost, error) { } err := cd.LoadRatingPlans() if err != nil { - utils.Logger.Err(fmt.Sprintf("error getting cost for key <%s>: %s", cd.GetKey(cd.Subject), err.Error())) + //utils.Logger.Err(fmt.Sprintf("error getting cost for key <%s>: %s", cd.GetKey(cd.Subject), err.Error())) return &CallCost{Cost: -1}, err } timespans := cd.splitInTimeSpans() @@ -616,8 +616,8 @@ func (origCD *CallDescriptor) getMaxSessionDuration(origAcc *Account) (time.Dura func (cd *CallDescriptor) GetMaxSessionDuration() (duration time.Duration, err error) { cd.account = nil // make sure it's not cached if account, err := cd.getAccount(); err != nil || account == nil { - utils.Logger.Err(fmt.Sprintf("Could not get account for <%s>: %s.", cd.GetAccountKey(), err.Error())) - return 0, err + utils.Logger.Err(fmt.Sprintf("Account: %s, not found", cd.GetAccountKey())) + return 0, utils.ErrAccountNotFound } else { if memberIds, err := account.GetUniqueSharedGroupMembers(cd); err == nil { if _, err := Guardian.Guard(func() (interface{}, error) { @@ -673,11 +673,11 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { cd.account = nil // make sure it's not cached // lock all group members if account, err := cd.getAccount(); err != nil || account == nil { - utils.Logger.Err(fmt.Sprintf("Could not get user balance for <%s>: %s.", cd.GetAccountKey(), err.Error())) - return nil, err + utils.Logger.Err(fmt.Sprintf("Account: %s, not found", cd.GetAccountKey())) + return nil, utils.ErrAccountNotFound } else { if memberIds, err := account.GetUniqueSharedGroupMembers(cd); err == nil { - Guardian.Guard(func() (interface{}, error) { + _, err = Guardian.Guard(func() (interface{}, error) { cc, err = cd.debit(account, false, true) return 0, err }, 0, memberIds...) @@ -695,8 +695,8 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { cd.account = nil // make sure it's not cached if account, err := cd.getAccount(); err != nil || account == nil { - utils.Logger.Err(fmt.Sprintf("Could not get user balance for <%s>: %s.", cd.GetAccountKey(), err.Error())) - return nil, utils.ErrUnauthorizedDestination + utils.Logger.Err(fmt.Sprintf("Account: %s, not found", cd.GetAccountKey())) + return nil, utils.ErrAccountNotFound } else { //log.Printf("ACC: %+v", account) if memberIds, err := account.GetUniqueSharedGroupMembers(cd); err == nil { diff --git a/utils/consts.go b/utils/consts.go index 7024a73e5..2a58fc381 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -26,6 +26,7 @@ var ( ErrInvalidPath = errors.New("INVALID_PATH") ErrInvalidKey = errors.New("INVALID_KEY") ErrUnauthorizedDestination = errors.New("UNAUTHORIZED_DESTINATION") + ErrAccountNotFound = errors.New("AccountNotFound") ) const ( From 40fa360ffbce6d96000ffbb8bd5cc6b5bf37eaf8 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 25 Nov 2015 19:55:42 +0200 Subject: [PATCH 03/30] fixed tests --- general_tests/auth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/general_tests/auth_test.go b/general_tests/auth_test.go index a47948eb2..d976c5c8e 100644 --- a/general_tests/auth_test.go +++ b/general_tests/auth_test.go @@ -97,7 +97,7 @@ func TestAuthPostpaidNoAcnt(t *testing.T) { Category: "call", Account: "nonexistent", Subject: "testauthpostpaid1", Destination: "4986517174963", SetupTime: time.Date(2015, 8, 27, 11, 26, 0, 0, time.UTC)} var maxSessionTime float64 - if err := rsponder.GetDerivedMaxSessionTime(cdr, &maxSessionTime); err == nil || err != utils.ErrNotFound { + if err := rsponder.GetDerivedMaxSessionTime(cdr, &maxSessionTime); err == nil || err != utils.ErrAccountNotFound { t.Error(err) } } From eece61187dc187976bf9d125d4ecc30207352365 Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 26 Nov 2015 12:28:44 +0100 Subject: [PATCH 04/30] Diameter AsSMGenericEvent to support metahandlers as well as constant values --- agents/libdmt.go | 108 ++++++++++++++++++------------ cdre/cdrexporter.go | 6 +- cdre/libcdre.go | 55 --------------- cdre/libcdre_test.go | 93 ------------------------- engine/cdrs.go | 3 +- engine/handler_derivedcharging.go | 1 + engine/suretax.go | 1 - utils/coreutils.go | 52 ++++++++++++++ utils/utils_test.go | 93 +++++++++++++++++++++++++ 9 files changed, 216 insertions(+), 196 deletions(-) diff --git a/agents/libdmt.go b/agents/libdmt.go index 81ee70fda..f635c8b4b 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -49,6 +49,7 @@ func init() { const ( META_CCR_USAGE = "*ccr_usage" META_CCR_SMG_EVENT_NAME = "*ccr_smg_event_name" + DIAMETER_CCR = "DIAMETER_CCR" ) func loadDictionaries(dictsDir, componentId string) error { @@ -331,57 +332,78 @@ func avpValAsString(a *diam.AVP) string { return dataVal[startIdx+1 : endIdx] } -// Extracts data out of CCR into a SMGenericEvent based on the configured template -func (self *CCR) AsSMGenericEvent(tpl []*config.CfgCdrField) (sessionmanager.SMGenericEvent, error) { - outMap := make(map[string]string) // work with it so we can append values to keys - if evName, err := self.metaHandler(META_CCR_SMG_EVENT_NAME, ""); err != nil { - return nil, err - } else { - outMap[utils.EVENT_NAME] = evName - } - for _, fldTpl := range tpl { - var outVal string - for _, rsrTpl := range fldTpl.Value { - if rsrTpl.IsStatic() { - outVal += rsrTpl.ParseValue("") - } else { - hierarchyPath := strings.Split(rsrTpl.Id, utils.HIERARCHY_SEP) - hpIf := make([]interface{}, len(hierarchyPath)) - for i, val := range hierarchyPath { - hpIf[i] = val - } - matchingAvps, err := self.diamMessage.FindAVPsWithPath(hpIf) - if err != nil || len(matchingAvps) == 0 { - utils.Logger.Warning(fmt.Sprintf(" Cannot find AVP for field template with id: %s, ignoring.", rsrTpl.Id)) - continue // Filter not matching - } - if matchingAvps[0].Data.Type() == diam.GroupedAVPType { - utils.Logger.Warning(fmt.Sprintf(" Value for field template with id: %s is matching a group AVP, not considering.", rsrTpl.Id)) - continue // Not convertible, ignore - } - outVal += avpValAsString(matchingAvps[0]) - } - } - if _, hasKey := outMap[fldTpl.FieldId]; !hasKey { - outMap[fldTpl.FieldId] = outVal - } else { // If already there, postpend - outMap[fldTpl.FieldId] += outVal - } - } - return sessionmanager.SMGenericEvent(utils.ConvertMapValStrIf(outMap)), nil -} - // Handler for meta functions func (self *CCR) metaHandler(tag, arg string) (string, error) { switch tag { - case META_CCR_SMG_EVENT_NAME: - return "", nil case META_CCR_USAGE: - return "", nil + usage := usageFromCCR(self.CCRequestType, self.CCRequestNumber, self.RequestedServiceUnit.CCTime, time.Duration(300)*time.Second) + return strconv.FormatFloat(usage.Seconds(), 'f', -1, 64), nil } return "", nil } +func (self *CCR) eventFieldValue(cfgFld *config.CfgCdrField) string { + var outVal string + for _, rsrTpl := range cfgFld.Value { + if rsrTpl.IsStatic() { + outVal += rsrTpl.ParseValue("") + } else { + hierarchyPath := strings.Split(rsrTpl.Id, utils.HIERARCHY_SEP) + hpIf := make([]interface{}, len(hierarchyPath)) + for i, val := range hierarchyPath { + hpIf[i] = val + } + matchingAvps, err := self.diamMessage.FindAVPsWithPath(hpIf) + if err != nil || len(matchingAvps) == 0 { + utils.Logger.Warning(fmt.Sprintf(" Cannot find AVP for field template with id: %s, ignoring.", rsrTpl.Id)) + continue // Filter not matching + } + if matchingAvps[0].Data.Type() == diam.GroupedAVPType { + utils.Logger.Warning(fmt.Sprintf(" Value for field template with id: %s is matching a group AVP, ignoring.", rsrTpl.Id)) + continue // Not convertible, ignore + } + outVal += avpValAsString(matchingAvps[0]) + } + } + return outVal +} + +// Extracts data out of CCR into a SMGenericEvent based on the configured template +func (self *CCR) AsSMGenericEvent(cfgFlds []*config.CfgCdrField) (sessionmanager.SMGenericEvent, error) { + outMap := make(map[string]string) // work with it so we can append values to keys + outMap[utils.EVENT_NAME] = DIAMETER_CCR + for _, cfgFld := range cfgFlds { + var outVal string + var err error + switch cfgFld.Type { + case utils.FILLER: + outVal = cfgFld.Value.Id() + cfgFld.Padding = "right" + case utils.CONSTANT: + outVal = cfgFld.Value.Id() + case utils.METATAG: + + outVal, err = self.metaHandler(cfgFld.MetatagId, cfgFld.Layout) + if err != nil { + utils.Logger.Warning(fmt.Sprintf(" Ignoring processing of metafunction: %s, error: %s", cfgFld.MetatagId, err.Error())) + } + case utils.CDRFIELD: + outVal = self.eventFieldValue(cfgFld) + } + fmtOut := outVal + if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + utils.Logger.Warning(fmt.Sprintf(" Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error())) + return nil, err + } + if _, hasKey := outMap[cfgFld.FieldId]; !hasKey { + outMap[cfgFld.FieldId] = fmtOut + } else { // If already there, postpend + outMap[cfgFld.FieldId] += fmtOut + } + } + return sessionmanager.SMGenericEvent(utils.ConvertMapValStrIf(outMap)), nil +} + func NewCCAFromCCR(ccr *CCR) *CCA { return &CCA{SessionId: ccr.SessionId, AuthApplicationId: ccr.AuthApplicationId, CCRequestType: ccr.CCRequestType, CCRequestNumber: ccr.CCRequestNumber, diamMessage: diam.NewMessage(ccr.diamMessage.Header.CommandCode, ccr.diamMessage.Header.CommandFlags&^diam.RequestFlag, ccr.diamMessage.Header.ApplicationID, diff --git a/cdre/cdrexporter.go b/cdre/cdrexporter.go index 8c28304b8..eb5c01ce4 100644 --- a/cdre/cdrexporter.go +++ b/cdre/cdrexporter.go @@ -271,7 +271,7 @@ func (cdre *CdrExporter) composeHeader() error { return err } fmtOut := outVal - if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Err(fmt.Sprintf(" Cannot export CDR header, field %s, error: %s", cfgFld.Tag, err.Error())) return err } @@ -300,7 +300,7 @@ func (cdre *CdrExporter) composeTrailer() error { return err } fmtOut := outVal - if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Err(fmt.Sprintf(" Cannot export CDR trailer, field: %s, error: %s", cfgFld.Tag, err.Error())) return err } @@ -366,7 +366,7 @@ func (cdre *CdrExporter) processCdr(cdr *engine.StoredCdr) error { return err } fmtOut := outVal - if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Err(fmt.Sprintf(" Cannot export CDR with cgrid: %s, runid: %s, fieldName: %s, fieldValue: %s, error: %s", cdr.CgrId, cdr.MediationRunId, cfgFld.Tag, outVal, err.Error())) return err } diff --git a/cdre/libcdre.go b/cdre/libcdre.go index f2ce17372..f2c52775a 100644 --- a/cdre/libcdre.go +++ b/cdre/libcdre.go @@ -19,64 +19,9 @@ along with this program. If not, see package cdre import ( - "errors" - "fmt" - "github.com/cgrates/cgrates/utils" ) -// Used as generic function logic for various fields - -// Attributes -// source - the base source -// width - the field width -// strip - if present it will specify the strip strategy, when missing strip will not be allowed -// padding - if present it will specify the padding strategy to use, left, right, zeroleft, zeroright -func FmtFieldWidth(source string, width int, strip, padding string, mandatory bool) (string, error) { - if mandatory && len(source) == 0 { - return "", errors.New("Empty source value") - } - if width == 0 { // Disable width processing if not defined - return source, nil - } - if len(source) == width { // the source is exactly the maximum length - return source, nil - } - if len(source) > width { //the source is bigger than allowed - if len(strip) == 0 { - return "", fmt.Errorf("Source %s is bigger than the width %d, no strip defied", source, width) - } - if strip == "right" { - return source[:width], nil - } else if strip == "xright" { - return source[:width-1] + "x", nil // Suffix with x to mark prefix - } else if strip == "left" { - diffIndx := len(source) - width - return source[diffIndx:], nil - } else if strip == "xleft" { // Prefix one x to mark stripping - diffIndx := len(source) - width - return "x" + source[diffIndx+1:], nil - } - } else { //the source is smaller as the maximum allowed - if len(padding) == 0 { - return "", fmt.Errorf("Source %s is smaller than the width %d, no padding defined", source, width) - } - var paddingFmt string - switch padding { - case "right": - paddingFmt = fmt.Sprintf("%%-%ds", width) - case "left": - paddingFmt = fmt.Sprintf("%%%ds", width) - case "zeroleft": - paddingFmt = fmt.Sprintf("%%0%ds", width) - } - if len(paddingFmt) != 0 { - return fmt.Sprintf(paddingFmt, source), nil - } - } - return source, nil -} - // Mask a number of characters in the suffix of the destination func MaskDestination(dest string, maskLen int) string { destLen := len(dest) diff --git a/cdre/libcdre_test.go b/cdre/libcdre_test.go index a7eb1bc85..2ceb6cfca 100644 --- a/cdre/libcdre_test.go +++ b/cdre/libcdre_test.go @@ -22,99 +22,6 @@ import ( "testing" ) -func TestMandatory(t *testing.T) { - _, err := FmtFieldWidth("", 0, "", "", true) - if err == nil { - t.Errorf("Failed to detect mandatory value") - } -} - -func TestMaxLen(t *testing.T) { - result, err := FmtFieldWidth("test", 4, "", "", false) - expected := "test" - if err != nil || result != expected { - t.Errorf("Expected \"test\" was \"%s\"", result) - } -} - -func TestRPadding(t *testing.T) { - result, err := FmtFieldWidth("test", 8, "", "right", false) - expected := "test " - if err != nil || result != expected { - t.Errorf("Expected \"%s \" was \"%s\"", expected, result) - } -} - -func TestPaddingFiller(t *testing.T) { - result, err := FmtFieldWidth("", 8, "", "right", false) - expected := " " - if err != nil || result != expected { - t.Errorf("Expected \"%s \" was \"%s\"", expected, result) - } -} - -func TestLPadding(t *testing.T) { - result, err := FmtFieldWidth("test", 8, "", "left", false) - expected := " test" - if err != nil || result != expected { - t.Errorf("Expected \"%s \" was \"%s\"", expected, result) - } -} - -func TestZeroLPadding(t *testing.T) { - result, err := FmtFieldWidth("test", 8, "", "zeroleft", false) - expected := "0000test" - if err != nil || result != expected { - t.Errorf("Expected \"%s \" was \"%s\"", expected, result) - } -} - -func TestRStrip(t *testing.T) { - result, err := FmtFieldWidth("test", 2, "right", "", false) - expected := "te" - if err != nil || result != expected { - t.Errorf("Expected \"%s \" was \"%s\"", expected, result) - } -} - -func TestXRStrip(t *testing.T) { - result, err := FmtFieldWidth("test", 3, "xright", "", false) - expected := "tex" - if err != nil || result != expected { - t.Errorf("Expected \"%s \" was \"%s\"", expected, result) - } -} - -func TestLStrip(t *testing.T) { - result, err := FmtFieldWidth("test", 2, "left", "", false) - expected := "st" - if err != nil || result != expected { - t.Errorf("Expected \"%s \" was \"%s\"", expected, result) - } -} - -func TestXLStrip(t *testing.T) { - result, err := FmtFieldWidth("test", 3, "xleft", "", false) - expected := "xst" - if err != nil || result != expected { - t.Errorf("Expected \"%s \" was \"%s\"", expected, result) - } -} - -func TestStripNotAllowed(t *testing.T) { - _, err := FmtFieldWidth("test", 3, "", "", false) - if err == nil { - t.Error("Expected error") - } -} - -func TestPaddingNotAllowed(t *testing.T) { - _, err := FmtFieldWidth("test", 5, "", "", false) - if err == nil { - t.Error("Expected error") - } -} - func TestMaskDestination(t *testing.T) { dest := "+4986517174963" if destMasked := MaskDestination(dest, 3); destMasked != "+4986517174***" { diff --git a/engine/cdrs.go b/engine/cdrs.go index 99d47226c..bb91737d9 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -273,7 +273,7 @@ func (self *CdrServer) deriveCdrs(storedCdr *StoredCdr) ([]*StoredCdr, error) { return cdrRuns, nil } attrsDC := &utils.AttrDerivedChargers{Tenant: storedCdr.Tenant, Category: storedCdr.Category, Direction: storedCdr.Direction, - Account: storedCdr.Account, Subject: storedCdr.Subject} + Account: storedCdr.Account, Subject: storedCdr.Subject, Destination: storedCdr.Destination} var dcs utils.DerivedChargers if err := self.rater.GetDerivedChargers(attrsDC, &dcs); err != nil { utils.Logger.Err(fmt.Sprintf("Could not get derived charging for cgrid %s, error: %s", storedCdr.CgrId, err.Error())) @@ -289,6 +289,7 @@ func (self *CdrServer) deriveCdrs(storedCdr *StoredCdr) ([]*StoredCdr, error) { } } if !matchingAllFilters { // Do not process the derived charger further if not all filters were matched + continue } dcReqTypeFld, _ := utils.NewRSRField(dc.ReqTypeField) diff --git a/engine/handler_derivedcharging.go b/engine/handler_derivedcharging.go index d5dc087f8..d008cb482 100644 --- a/engine/handler_derivedcharging.go +++ b/engine/handler_derivedcharging.go @@ -19,6 +19,7 @@ along with this program. If not, see package engine import ( + "fmt" "github.com/cgrates/cgrates/cache2go" "github.com/cgrates/cgrates/utils" ) diff --git a/engine/suretax.go b/engine/suretax.go index f58758699..cbf7d3666 100644 --- a/engine/suretax.go +++ b/engine/suretax.go @@ -195,7 +195,6 @@ func SureTaxProcessCdr(cdr *StoredCdr) error { if err != nil { return err } - utils.Logger.Debug(fmt.Sprintf("NewSureTaxRequest: %s\n", string(jsnContent))) resp, err := sureTaxClient.Post(stCfg.Url, "application/json", bytes.NewBuffer(jsnContent)) if err != nil { return err diff --git a/utils/coreutils.go b/utils/coreutils.go index 6a2286f0a..365047007 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -421,3 +421,55 @@ func Clone(a, b interface{}) error { } return nil } + +// Used as generic function logic for various fields + +// Attributes +// source - the base source +// width - the field width +// strip - if present it will specify the strip strategy, when missing strip will not be allowed +// padding - if present it will specify the padding strategy to use, left, right, zeroleft, zeroright +func FmtFieldWidth(source string, width int, strip, padding string, mandatory bool) (string, error) { + if mandatory && len(source) == 0 { + return "", errors.New("Empty source value") + } + if width == 0 { // Disable width processing if not defined + return source, nil + } + if len(source) == width { // the source is exactly the maximum length + return source, nil + } + if len(source) > width { //the source is bigger than allowed + if len(strip) == 0 { + return "", fmt.Errorf("Source %s is bigger than the width %d, no strip defied", source, width) + } + if strip == "right" { + return source[:width], nil + } else if strip == "xright" { + return source[:width-1] + "x", nil // Suffix with x to mark prefix + } else if strip == "left" { + diffIndx := len(source) - width + return source[diffIndx:], nil + } else if strip == "xleft" { // Prefix one x to mark stripping + diffIndx := len(source) - width + return "x" + source[diffIndx+1:], nil + } + } else { //the source is smaller as the maximum allowed + if len(padding) == 0 { + return "", fmt.Errorf("Source %s is smaller than the width %d, no padding defined", source, width) + } + var paddingFmt string + switch padding { + case "right": + paddingFmt = fmt.Sprintf("%%-%ds", width) + case "left": + paddingFmt = fmt.Sprintf("%%%ds", width) + case "zeroleft": + paddingFmt = fmt.Sprintf("%%0%ds", width) + } + if len(paddingFmt) != 0 { + return fmt.Sprintf(paddingFmt, source), nil + } + } + return source, nil +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 8d6dcc657..1f5951c07 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -486,3 +486,96 @@ func TestConvertIfaceToString(t *testing.T) { t.Error(resVal, converted) } } + +func TestMandatory(t *testing.T) { + _, err := FmtFieldWidth("", 0, "", "", true) + if err == nil { + t.Errorf("Failed to detect mandatory value") + } +} + +func TestMaxLen(t *testing.T) { + result, err := FmtFieldWidth("test", 4, "", "", false) + expected := "test" + if err != nil || result != expected { + t.Errorf("Expected \"test\" was \"%s\"", result) + } +} + +func TestRPadding(t *testing.T) { + result, err := FmtFieldWidth("test", 8, "", "right", false) + expected := "test " + if err != nil || result != expected { + t.Errorf("Expected \"%s \" was \"%s\"", expected, result) + } +} + +func TestPaddingFiller(t *testing.T) { + result, err := FmtFieldWidth("", 8, "", "right", false) + expected := " " + if err != nil || result != expected { + t.Errorf("Expected \"%s \" was \"%s\"", expected, result) + } +} + +func TestLPadding(t *testing.T) { + result, err := FmtFieldWidth("test", 8, "", "left", false) + expected := " test" + if err != nil || result != expected { + t.Errorf("Expected \"%s \" was \"%s\"", expected, result) + } +} + +func TestZeroLPadding(t *testing.T) { + result, err := FmtFieldWidth("test", 8, "", "zeroleft", false) + expected := "0000test" + if err != nil || result != expected { + t.Errorf("Expected \"%s \" was \"%s\"", expected, result) + } +} + +func TestRStrip(t *testing.T) { + result, err := FmtFieldWidth("test", 2, "right", "", false) + expected := "te" + if err != nil || result != expected { + t.Errorf("Expected \"%s \" was \"%s\"", expected, result) + } +} + +func TestXRStrip(t *testing.T) { + result, err := FmtFieldWidth("test", 3, "xright", "", false) + expected := "tex" + if err != nil || result != expected { + t.Errorf("Expected \"%s \" was \"%s\"", expected, result) + } +} + +func TestLStrip(t *testing.T) { + result, err := FmtFieldWidth("test", 2, "left", "", false) + expected := "st" + if err != nil || result != expected { + t.Errorf("Expected \"%s \" was \"%s\"", expected, result) + } +} + +func TestXLStrip(t *testing.T) { + result, err := FmtFieldWidth("test", 3, "xleft", "", false) + expected := "xst" + if err != nil || result != expected { + t.Errorf("Expected \"%s \" was \"%s\"", expected, result) + } +} + +func TestStripNotAllowed(t *testing.T) { + _, err := FmtFieldWidth("test", 3, "", "", false) + if err == nil { + t.Error("Expected error") + } +} + +func TestPaddingNotAllowed(t *testing.T) { + _, err := FmtFieldWidth("test", 5, "", "", false) + if err == nil { + t.Error("Expected error") + } +} From 5247c43beaf2945fd2fd994f423611dae4fad464 Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 26 Nov 2015 12:32:19 +0100 Subject: [PATCH 05/30] Fix Diameter test --- agents/dmtagent_it_test.go | 2 +- engine/handler_derivedcharging.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 7276f56c9..0e1816863 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -131,7 +131,7 @@ func TestDmtAgentCCRAsSMGenericEvent(t *testing.T) { if ccr.diamMessage, err = ccr.AsDiameterMessage(); err != nil { t.Error(err) } - eSMGE := sessionmanager.SMGenericEvent{"AccId": "routinga;1442095190;1476802709", + eSMGE := sessionmanager.SMGenericEvent{"EventName": "DIAMETER_CCR", "AccId": "routinga;1442095190;1476802709", "Account": "*users", "AnswerTime": "2015-11-23 12:22:24 +0000 UTC", "Category": "call_4912395676749123956767", "Destination": "4986517174964", "Direction": "*out", "ReqType": "*users", "SetupTime": "2015-11-23 12:22:24 +0000 UTC", "Subject": "*users", "SubscriberId": "4986517174963", "TOR": "*voice", "Tenant": "*users", "Usage": "300"} diff --git a/engine/handler_derivedcharging.go b/engine/handler_derivedcharging.go index d008cb482..d5dc087f8 100644 --- a/engine/handler_derivedcharging.go +++ b/engine/handler_derivedcharging.go @@ -19,7 +19,6 @@ along with this program. If not, see package engine import ( - "fmt" "github.com/cgrates/cgrates/cache2go" "github.com/cgrates/cgrates/utils" ) From 17eaa13d017e52af83b203f4ce465c3eabf68e46 Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 26 Nov 2015 13:46:44 +0100 Subject: [PATCH 06/30] CDRC, CDRE and Diameter config templates change: cdrfield->*composed, metatag->*handler, constant->*constant, http_post->*http_post, MetaTagId->HandlerId --- agents/libdmt.go | 13 +- cdrc/csv.go | 4 +- cdrc/csv_test.go | 8 +- cdrc/fwv.go | 4 +- cdre/cdrexporter.go | 31 ++-- cdre/fixedwidth_test.go | 74 ++++---- config/cfgcdrfield.go | 6 +- config/config_defaults.go | 80 ++++----- config/config_json_test.go | 80 ++++----- config/configcdrc_test.go | 72 ++++---- config/libconfig_json.go | 2 +- utils/consts.go | 338 ++++++++++++++++++------------------- 12 files changed, 355 insertions(+), 357 deletions(-) diff --git a/agents/libdmt.go b/agents/libdmt.go index f635c8b4b..5f0aaa5f3 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -376,18 +376,17 @@ func (self *CCR) AsSMGenericEvent(cfgFlds []*config.CfgCdrField) (sessionmanager var outVal string var err error switch cfgFld.Type { - case utils.FILLER: + case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" - case utils.CONSTANT: + case utils.META_CONSTANT: outVal = cfgFld.Value.Id() - case utils.METATAG: - - outVal, err = self.metaHandler(cfgFld.MetatagId, cfgFld.Layout) + case utils.META_HANDLER: + outVal, err = self.metaHandler(cfgFld.HandlerId, cfgFld.Layout) if err != nil { - utils.Logger.Warning(fmt.Sprintf(" Ignoring processing of metafunction: %s, error: %s", cfgFld.MetatagId, err.Error())) + utils.Logger.Warning(fmt.Sprintf(" Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error())) } - case utils.CDRFIELD: + case utils.META_COMPOSED: outVal = self.eventFieldValue(cfgFld) } fmtOut := outVal diff --git a/cdrc/csv.go b/cdrc/csv.go index fbe7a5c9f..66d5971d5 100644 --- a/cdrc/csv.go +++ b/cdrc/csv.go @@ -292,7 +292,7 @@ func (self *CsvRecordsProcessor) recordToStoredCdr(record []string, cdrcId strin } var fieldVal string - if cdrFldCfg.Type == utils.CDRFIELD { + if cdrFldCfg.Type == utils.META_COMPOSED { for _, cfgFieldRSR := range cdrFldCfg.Value { if cfgFieldRSR.IsStatic() { fieldVal += cfgFieldRSR.ParseValue("") @@ -304,7 +304,7 @@ func (self *CsvRecordsProcessor) recordToStoredCdr(record []string, cdrcId strin } } } - } else if cdrFldCfg.Type == utils.HTTP_POST { + } else if cdrFldCfg.Type == utils.META_HTTP_POST { lazyHttpFields = append(lazyHttpFields, cdrFldCfg) // Will process later so we can send an estimation of storedCdr to http server } else { return nil, fmt.Errorf("Unsupported field type: %s", cdrFldCfg.Type) diff --git a/cdrc/csv_test.go b/cdrc/csv_test.go index 58e4cd8cb..286eb0457 100644 --- a/cdrc/csv_test.go +++ b/cdrc/csv_test.go @@ -32,8 +32,8 @@ func TestCsvRecordForkCdr(t *testing.T) { cgrConfig, _ := config.NewDefaultCGRConfig() cdrcConfig := cgrConfig.CdrcProfiles["/var/log/cgrates/cdrc/in"][utils.META_DEFAULT] cdrcConfig.CdrSourceId = "TEST_CDRC" - cdrcConfig.ContentFields = append(cdrcConfig.ContentFields, &config.CfgCdrField{Tag: "SupplierTest", Type: utils.CDRFIELD, FieldId: utils.SUPPLIER, Value: []*utils.RSRField{&utils.RSRField{Id: "14"}}}) - cdrcConfig.ContentFields = append(cdrcConfig.ContentFields, &config.CfgCdrField{Tag: "DisconnectCauseTest", Type: utils.CDRFIELD, FieldId: utils.DISCONNECT_CAUSE, + cdrcConfig.ContentFields = append(cdrcConfig.ContentFields, &config.CfgCdrField{Tag: "SupplierTest", Type: utils.META_COMPOSED, FieldId: utils.SUPPLIER, Value: []*utils.RSRField{&utils.RSRField{Id: "14"}}}) + cdrcConfig.ContentFields = append(cdrcConfig.ContentFields, &config.CfgCdrField{Tag: "DisconnectCauseTest", Type: utils.META_COMPOSED, FieldId: utils.DISCONNECT_CAUSE, Value: []*utils.RSRField{&utils.RSRField{Id: "16"}}}) // csvProcessor := &CsvRecordsProcessor{dfltCdrcCfg: cdrcConfig, cdrcCfgs: map[string]*config.CdrcConfig{"*default": cdrcConfig}} @@ -78,8 +78,8 @@ func TestCsvDataMultiplyFactor(t *testing.T) { cgrConfig, _ := config.NewDefaultCGRConfig() cdrcConfig := cgrConfig.CdrcProfiles["/var/log/cgrates/cdrc/in"][utils.META_DEFAULT] cdrcConfig.CdrSourceId = "TEST_CDRC" - cdrcConfig.ContentFields = []*config.CfgCdrField{&config.CfgCdrField{Tag: "TORField", Type: utils.CDRFIELD, FieldId: utils.TOR, Value: []*utils.RSRField{&utils.RSRField{Id: "0"}}}, - &config.CfgCdrField{Tag: "UsageField", Type: utils.CDRFIELD, FieldId: utils.USAGE, Value: []*utils.RSRField{&utils.RSRField{Id: "1"}}}} + cdrcConfig.ContentFields = []*config.CfgCdrField{&config.CfgCdrField{Tag: "TORField", Type: utils.META_COMPOSED, FieldId: utils.TOR, Value: []*utils.RSRField{&utils.RSRField{Id: "0"}}}, + &config.CfgCdrField{Tag: "UsageField", Type: utils.META_COMPOSED, FieldId: utils.USAGE, Value: []*utils.RSRField{&utils.RSRField{Id: "1"}}}} csvProcessor := &CsvRecordsProcessor{dfltCdrcCfg: cdrcConfig, cdrcCfgs: map[string]*config.CdrcConfig{"*default": cdrcConfig}} csvProcessor.cdrcCfgs["*default"].DataUsageMultiplyFactor = 0 cdrRow := []string{"*data", "1"} diff --git a/cdrc/fwv.go b/cdrc/fwv.go index aa1f78cea..ddb84241f 100644 --- a/cdrc/fwv.go +++ b/cdrc/fwv.go @@ -180,7 +180,7 @@ func (self *FwvRecordsProcessor) recordToStoredCdr(record string, cfgKey string) for _, cdrFldCfg := range cfgFields { var fieldVal string switch cdrFldCfg.Type { - case utils.CDRFIELD: + case utils.META_COMPOSED: for _, cfgFieldRSR := range cdrFldCfg.Value { if cfgFieldRSR.IsStatic() { fieldVal += cfgFieldRSR.ParseValue("") @@ -192,7 +192,7 @@ func (self *FwvRecordsProcessor) recordToStoredCdr(record string, cfgKey string) } } } - case utils.HTTP_POST: + case utils.META_HTTP_POST: lazyHttpFields = append(lazyHttpFields, cdrFldCfg) // Will process later so we can send an estimation of storedCdr to http server default: //return nil, fmt.Errorf("Unsupported field type: %s", cdrFldCfg.Type) diff --git a/cdre/cdrexporter.go b/cdre/cdrexporter.go index eb5c01ce4..0e9fddc81 100644 --- a/cdre/cdrexporter.go +++ b/cdre/cdrexporter.go @@ -33,8 +33,9 @@ import ( ) const ( - COST_DETAILS = "cost_details" - DATETIME = "datetime" + COST_DETAILS = "cost_details" + //DATETIME = "datetime" + META_DATETIME = "*datetime" META_EXPORTID = "*export_id" META_TIMENOW = "*time_now" META_FIRSTCDRATIME = "*first_cdr_atime" @@ -256,12 +257,12 @@ func (cdre *CdrExporter) composeHeader() error { for _, cfgFld := range cdre.exportTemplate.HeaderFields { var outVal string switch cfgFld.Type { - case utils.FILLER: + case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" - case utils.CONSTANT: + case utils.META_CONSTANT: outVal = cfgFld.Value.Id() - case utils.METATAG: + case utils.META_HANDLER: outVal, err = cdre.metaHandler(cfgFld.Value.Id(), cfgFld.Layout) default: return fmt.Errorf("Unsupported field type: %s", cfgFld.Type) @@ -285,12 +286,12 @@ func (cdre *CdrExporter) composeTrailer() error { for _, cfgFld := range cdre.exportTemplate.TrailerFields { var outVal string switch cfgFld.Type { - case utils.FILLER: + case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" - case utils.CONSTANT: + case utils.META_CONSTANT: outVal = cfgFld.Value.Id() - case utils.METATAG: + case utils.META_HANDLER: outVal, err = cdre.metaHandler(cfgFld.Value.Id(), cfgFld.Layout) default: return fmt.Errorf("Unsupported field type: %s", cfgFld.Type) @@ -332,16 +333,16 @@ func (cdre *CdrExporter) processCdr(cdr *engine.StoredCdr) error { for idx, cfgFld := range cdre.exportTemplate.ContentFields { var outVal string switch cfgFld.Type { - case utils.FILLER: + case utils.META_FILLER: outVal = cfgFld.Value.Id() cfgFld.Padding = "right" - case utils.CONSTANT: + case utils.META_CONSTANT: outVal = cfgFld.Value.Id() - case utils.CDRFIELD: + case utils.META_COMPOSED: outVal, err = cdre.cdrFieldValue(cdr, cfgFld) - case DATETIME: + case META_DATETIME: outVal, err = cdre.getDateTimeFieldVal(cdr, cfgFld) - case utils.HTTP_POST: + case utils.META_HTTP_POST: var outValByte []byte httpAddr := cfgFld.Value.Id() if len(httpAddr) == 0 { @@ -352,9 +353,9 @@ func (cdre *CdrExporter) processCdr(cdr *engine.StoredCdr) error { err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Tag) } } - case utils.COMBIMED: + case utils.META_COMBIMED: outVal, err = cdre.getCombimedCdrFieldVal(cdr, cfgFld) - case utils.METATAG: + case utils.META_HANDLER: if cfgFld.Value.Id() == META_MASKDESTINATION { outVal, err = cdre.metaHandler(cfgFld.Value.Id(), cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION})) } else { diff --git a/cdre/fixedwidth_test.go b/cdre/fixedwidth_test.go index 6c1c39c43..f0e15a71f 100644 --- a/cdre/fixedwidth_test.go +++ b/cdre/fixedwidth_test.go @@ -30,70 +30,70 @@ import ( ) var hdrJsnCfgFlds = []*config.CdrFieldJsonCfg{ - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("10"), Width: utils.IntPointer(2)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler1"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(3)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("DistributorCode"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("VOI"), Width: utils.IntPointer(3)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FileSeqNr"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_EXPORTID), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("10"), Width: utils.IntPointer(2)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler1"), Type: utils.StringPointer(utils.META_FILLER), Width: utils.IntPointer(3)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("DistributorCode"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("VOI"), Width: utils.IntPointer(3)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FileSeqNr"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_EXPORTID), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("zeroleft")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("LastCdr"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_LASTCDRATIME), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("LastCdr"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_LASTCDRATIME), Width: utils.IntPointer(12), Layout: utils.StringPointer("020106150400")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FileCreationfTime"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_TIMENOW), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FileCreationfTime"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_TIMENOW), Width: utils.IntPointer(12), Layout: utils.StringPointer("020106150400")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FileVersion"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("01"), Width: utils.IntPointer(2)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler2"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(105)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FileVersion"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("01"), Width: utils.IntPointer(2)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler2"), Type: utils.StringPointer(utils.META_FILLER), Width: utils.IntPointer(105)}, } var contentJsnCfgFlds = []*config.CdrFieldJsonCfg{ - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("20"), Width: utils.IntPointer(2)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Account"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.ACCOUNT), Width: utils.IntPointer(12), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("20"), Width: utils.IntPointer(2)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Account"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer(utils.ACCOUNT), Width: utils.IntPointer(12), Strip: utils.StringPointer("left"), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Subject"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.SUBJECT), Width: utils.IntPointer(5), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Subject"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer(utils.SUBJECT), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("CLI"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer("cli"), Width: utils.IntPointer(15), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("CLI"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("cli"), Width: utils.IntPointer(15), Strip: utils.StringPointer("xright"), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Destination"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.DESTINATION), Width: utils.IntPointer(24), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Destination"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer(utils.DESTINATION), Width: utils.IntPointer(24), Strip: utils.StringPointer("xright"), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TOR"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("02"), Width: utils.IntPointer(2)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("SubtypeTOR"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("11"), Width: utils.IntPointer(4), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TOR"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("02"), Width: utils.IntPointer(2)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("SubtypeTOR"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("11"), Width: utils.IntPointer(4), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("SetupTime"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.SETUP_TIME), Width: utils.IntPointer(12), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("SetupTime"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer(utils.SETUP_TIME), Width: utils.IntPointer(12), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right"), Layout: utils.StringPointer("020106150400")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Duration"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.USAGE), Width: utils.IntPointer(6), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Duration"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer(utils.USAGE), Width: utils.IntPointer(6), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right"), Layout: utils.StringPointer(utils.SECONDS)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("DataVolume"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(6)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TaxCode"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("1"), Width: utils.IntPointer(1)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("OperatorCode"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer("opercode"), Width: utils.IntPointer(2), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("DataVolume"), Type: utils.StringPointer(utils.META_FILLER), Width: utils.IntPointer(6)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TaxCode"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("1"), Width: utils.IntPointer(1)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("OperatorCode"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("opercode"), Width: utils.IntPointer(2), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("ProductId"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer("productid"), Width: utils.IntPointer(5), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("ProductId"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("productid"), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("NetworkId"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("3"), Width: utils.IntPointer(1)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("CallId"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.ACCID), Width: utils.IntPointer(16), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("NetworkId"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("3"), Width: utils.IntPointer(1)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("CallId"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer(utils.ACCID), Width: utils.IntPointer(16), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(8)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(8)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TerminationCode"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer("operator;product"), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler"), Type: utils.StringPointer(utils.META_FILLER), Width: utils.IntPointer(8)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler"), Type: utils.StringPointer(utils.META_FILLER), Width: utils.IntPointer(8)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TerminationCode"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("operator;product"), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Cost"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.COST), Width: utils.IntPointer(9), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Cost"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer(utils.COST), Width: utils.IntPointer(9), Padding: utils.StringPointer("zeroleft")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("DestinationPrivacy"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_MASKDESTINATION), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("DestinationPrivacy"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_MASKDESTINATION), Width: utils.IntPointer(1)}, } var trailerJsnCfgFlds = []*config.CdrFieldJsonCfg{ - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("90"), Width: utils.IntPointer(2)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler1"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(3)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("DistributorCode"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("VOI"), Width: utils.IntPointer(3)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FileSeqNr"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_EXPORTID), Width: utils.IntPointer(5), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("90"), Width: utils.IntPointer(2)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler1"), Type: utils.StringPointer(utils.META_FILLER), Width: utils.IntPointer(3)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("DistributorCode"), Type: utils.StringPointer(utils.META_CONSTANT), Value: utils.StringPointer("VOI"), Width: utils.IntPointer(3)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FileSeqNr"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_EXPORTID), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("zeroleft")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("NumberOfRecords"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_NRCDRS), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("NumberOfRecords"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_NRCDRS), Width: utils.IntPointer(6), Padding: utils.StringPointer("zeroleft")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("CdrsDuration"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_DURCDRS), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("CdrsDuration"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_DURCDRS), Width: utils.IntPointer(8), Padding: utils.StringPointer("zeroleft"), Layout: utils.StringPointer(utils.SECONDS)}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FirstCdrTime"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_FIRSTCDRATIME), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("FirstCdrTime"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_FIRSTCDRATIME), Width: utils.IntPointer(12), Layout: utils.StringPointer("020106150400")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("LastCdrTime"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_LASTCDRATIME), + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("LastCdrTime"), Type: utils.StringPointer(utils.META_HANDLER), Value: utils.StringPointer(META_LASTCDRATIME), Width: utils.IntPointer(12), Layout: utils.StringPointer("020106150400")}, - &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler2"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(93)}, + &config.CdrFieldJsonCfg{Tag: utils.StringPointer("Filler2"), Type: utils.StringPointer(utils.META_FILLER), Width: utils.IntPointer(93)}, } var hdrCfgFlds, contentCfgFlds, trailerCfgFlds []*config.CfgCdrField diff --git a/config/cfgcdrfield.go b/config/cfgcdrfield.go index f7e554b7e..ddaac583a 100644 --- a/config/cfgcdrfield.go +++ b/config/cfgcdrfield.go @@ -34,8 +34,8 @@ func NewCfgCdrFieldFromCdrFieldJsonCfg(jsnCfgFld *CdrFieldJsonCfg) (*CfgCdrField if jsnCfgFld.Field_id != nil { cfgFld.FieldId = *jsnCfgFld.Field_id } - if jsnCfgFld.Metatag_id != nil { - cfgFld.MetatagId = *jsnCfgFld.Metatag_id + if jsnCfgFld.Handler_id != nil { + cfgFld.HandlerId = *jsnCfgFld.Handler_id } if jsnCfgFld.Value != nil { if cfgFld.Value, err = utils.ParseRSRFields(*jsnCfgFld.Value, utils.INFIELD_SEP); err != nil { @@ -69,7 +69,7 @@ type CfgCdrField struct { Tag string // Identifier for the administrator Type string // Type of field FieldId string // Field identifier - MetatagId string + HandlerId string Value utils.RSRFields FieldFilter utils.RSRFields Width int diff --git a/config/config_defaults.go b/config/config_defaults.go index ab015b675..522e564f9 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -140,21 +140,21 @@ const CGRATES_CFG_JSON = ` "export_dir": "/var/log/cgrates/cdre", // path where the exported CDRs will be placed "header_fields": [], // template of the exported header fields "content_fields": [ // template of the exported content fields - {"tag": "CgrId", "field_id": "CgrId", "type": "cdrfield", "value": "CgrId"}, - {"tag":"RunId", "field_id": "MediationRunId", "type": "cdrfield", "value": "MediationRunId"}, - {"tag":"Tor", "field_id": "TOR", "type": "cdrfield", "value": "TOR"}, - {"tag":"AccId", "field_id": "AccId", "type": "cdrfield", "value": "AccId"}, - {"tag":"ReqType", "field_id": "ReqType", "type": "cdrfield", "value": "ReqType"}, - {"tag":"Direction", "field_id": "Direction", "type": "cdrfield", "value": "Direction"}, - {"tag":"Tenant", "field_id": "Tenant", "type": "cdrfield", "value": "Tenant"}, - {"tag":"Category", "field_id": "Category", "type": "cdrfield", "value": "Category"}, - {"tag":"Account", "field_id": "Account", "type": "cdrfield", "value": "Account"}, - {"tag":"Subject", "field_id": "Subject", "type": "cdrfield", "value": "Subject"}, - {"tag":"Destination", "field_id": "Destination", "type": "cdrfield", "value": "Destination"}, - {"tag":"SetupTime", "field_id": "SetupTime", "type": "cdrfield", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00"}, - {"tag":"AnswerTime", "field_id": "AnswerTime", "type": "cdrfield", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"}, - {"tag":"Usage", "field_id": "Usage", "type": "cdrfield", "value": "Usage"}, - {"tag":"Cost", "field_id": "Cost", "type": "cdrfield", "value": "Cost"}, + {"tag": "CgrId", "field_id": "CgrId", "type": "*composed", "value": "CgrId"}, + {"tag":"RunId", "field_id": "MediationRunId", "type": "*composed", "value": "MediationRunId"}, + {"tag":"Tor", "field_id": "TOR", "type": "*composed", "value": "TOR"}, + {"tag":"AccId", "field_id": "AccId", "type": "*composed", "value": "AccId"}, + {"tag":"ReqType", "field_id": "ReqType", "type": "*composed", "value": "ReqType"}, + {"tag":"Direction", "field_id": "Direction", "type": "*composed", "value": "Direction"}, + {"tag":"Tenant", "field_id": "Tenant", "type": "*composed", "value": "Tenant"}, + {"tag":"Category", "field_id": "Category", "type": "*composed", "value": "Category"}, + {"tag":"Account", "field_id": "Account", "type": "*composed", "value": "Account"}, + {"tag":"Subject", "field_id": "Subject", "type": "*composed", "value": "Subject"}, + {"tag":"Destination", "field_id": "Destination", "type": "*composed", "value": "Destination"}, + {"tag":"SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00"}, + {"tag":"AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"}, + {"tag":"Usage", "field_id": "Usage", "type": "*composed", "value": "Usage"}, + {"tag":"Cost", "field_id": "Cost", "type": "*composed", "value": "Cost"}, ], "trailer_fields": [], // template of the exported trailer fields } @@ -181,18 +181,18 @@ const CGRATES_CFG_JSON = ` "partial_record_cache": "10s", // duration to cache partial records when not pairing "header_fields": [], // template of the import header fields "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value - {"tag": "tor", "field_id": "TOR", "type": "cdrfield", "value": "2", "mandatory": true}, - {"tag": "accid", "field_id": "AccId", "type": "cdrfield", "value": "3", "mandatory": true}, - {"tag": "reqtype", "field_id": "ReqType", "type": "cdrfield", "value": "4", "mandatory": true}, - {"tag": "direction", "field_id": "Direction", "type": "cdrfield", "value": "5", "mandatory": true}, - {"tag": "tenant", "field_id": "Tenant", "type": "cdrfield", "value": "6", "mandatory": true}, - {"tag": "category", "field_id": "Category", "type": "cdrfield", "value": "7", "mandatory": true}, - {"tag": "account", "field_id": "Account", "type": "cdrfield", "value": "8", "mandatory": true}, - {"tag": "subject", "field_id": "Subject", "type": "cdrfield", "value": "9", "mandatory": true}, - {"tag": "destination", "field_id": "Destination", "type": "cdrfield", "value": "10", "mandatory": true}, - {"tag": "setup_time", "field_id": "SetupTime", "type": "cdrfield", "value": "11", "mandatory": true}, - {"tag": "answer_time", "field_id": "AnswerTime", "type": "cdrfield", "value": "12", "mandatory": true}, - {"tag": "usage", "field_id": "Usage", "type": "cdrfield", "value": "13", "mandatory": true}, + {"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "2", "mandatory": true}, + {"tag": "accid", "field_id": "AccId", "type": "*composed", "value": "3", "mandatory": true}, + {"tag": "reqtype", "field_id": "ReqType", "type": "*composed", "value": "4", "mandatory": true}, + {"tag": "direction", "field_id": "Direction", "type": "*composed", "value": "5", "mandatory": true}, + {"tag": "tenant", "field_id": "Tenant", "type": "*composed", "value": "6", "mandatory": true}, + {"tag": "category", "field_id": "Category", "type": "*composed", "value": "7", "mandatory": true}, + {"tag": "account", "field_id": "Account", "type": "*composed", "value": "8", "mandatory": true}, + {"tag": "subject", "field_id": "Subject", "type": "*composed", "value": "9", "mandatory": true}, + {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "10", "mandatory": true}, + {"tag": "setup_time", "field_id": "SetupTime", "type": "*composed", "value": "11", "mandatory": true}, + {"tag": "answer_time", "field_id": "AnswerTime", "type": "*composed", "value": "12", "mandatory": true}, + {"tag": "usage", "field_id": "Usage", "type": "*composed", "value": "13", "mandatory": true}, ], "trailer_fields": [], // template of the import trailer fields } @@ -276,19 +276,19 @@ const CGRATES_CFG_JSON = ` "request_filter": "Subscription-Id>Subscription-Type(0)", // filter requests processed by this processor "continue_on_success": false, // continue to the next template if executed "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value - {"tag": "tor", "field_id": "TOR", "type": "cdrfield", "value": "^*voice", "mandatory": true}, - {"tag": "accid", "field_id": "AccId", "type": "cdrfield", "value": "Session-Id", "mandatory": true}, - {"tag": "reqtype", "field_id": "ReqType", "type": "cdrfield", "value": "^*users", "mandatory": true}, - {"tag": "direction", "field_id": "Direction", "type": "cdrfield", "value": "^*out", "mandatory": true}, - {"tag": "tenant", "field_id": "Tenant", "type": "cdrfield", "value": "^*users", "mandatory": true}, - {"tag": "category", "field_id": "Category", "type": "cdrfield", "value": "^call_;~Service-Information>IN-Information>Calling-Vlr-Number:s/^$/33000/;~Service-Information>IN-Information>Calling-Vlr-Number:s/^(\\d{5})/${1}/", "mandatory": true}, - {"tag": "account", "field_id": "Account", "type": "cdrfield", "value": "^*users", "mandatory": true}, - {"tag": "subject", "field_id": "Subject", "type": "cdrfield", "value": "^*users", "mandatory": true}, - {"tag": "destination", "field_id": "Destination", "type": "cdrfield", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, - {"tag": "setup_time", "field_id": "SetupTime", "type": "cdrfield", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "answer_time", "field_id": "AnswerTime", "type": "cdrfield", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "usage", "field_id": "Usage", "type": "cdrfield", "value": "Requested-Service-Unit>CC-Time", "mandatory": true}, - {"tag": "subscriber_id", "field_id": "SubscriberId", "type": "cdrfield", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, + {"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "^*voice", "mandatory": true}, + {"tag": "accid", "field_id": "AccId", "type": "*composed", "value": "Session-Id", "mandatory": true}, + {"tag": "reqtype", "field_id": "ReqType", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, + {"tag": "tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "category", "field_id": "Category", "type": "*composed", "value": "^call_;~Service-Information>IN-Information>Calling-Vlr-Number:s/^$/33000/;~Service-Information>IN-Information>Calling-Vlr-Number:s/^(\\d{5})/${1}/", "mandatory": true}, + {"tag": "account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, + {"tag": "setup_time", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "answer_time", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "usage", "field_id": "Usage", "type": "*composed", "value": "Requested-Service-Unit>CC-Time", "mandatory": true}, + {"tag": "subscriber_id", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, ], }, ], diff --git a/config/config_json_test.go b/config/config_json_test.go index f122227c8..39cdfd5b5 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -180,65 +180,65 @@ func TestDfCdreJsonCfgs(t *testing.T) { eContentFlds := []*CdrFieldJsonCfg{ &CdrFieldJsonCfg{Tag: utils.StringPointer("CgrId"), Field_id: utils.StringPointer(utils.CGRID), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.CGRID)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("RunId"), Field_id: utils.StringPointer(utils.MEDI_RUNID), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.MEDI_RUNID)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Tor"), Field_id: utils.StringPointer(utils.TOR), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.TOR)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("AccId"), Field_id: utils.StringPointer(utils.ACCID), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.ACCID)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("ReqType"), Field_id: utils.StringPointer(utils.REQTYPE), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.REQTYPE)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Direction"), Field_id: utils.StringPointer(utils.DIRECTION), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.DIRECTION)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Tenant"), Field_id: utils.StringPointer(utils.TENANT), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.TENANT)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Category"), Field_id: utils.StringPointer(utils.CATEGORY), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.CATEGORY)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Account"), Field_id: utils.StringPointer(utils.ACCOUNT), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.ACCOUNT)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Subject"), Field_id: utils.StringPointer(utils.SUBJECT), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.SUBJECT)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Destination"), Field_id: utils.StringPointer(utils.DESTINATION), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.DESTINATION)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("SetupTime"), Field_id: utils.StringPointer(utils.SETUP_TIME), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.SETUP_TIME), Layout: utils.StringPointer("2006-01-02T15:04:05Z07:00")}, &CdrFieldJsonCfg{Tag: utils.StringPointer("AnswerTime"), Field_id: utils.StringPointer(utils.ANSWER_TIME), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.ANSWER_TIME), Layout: utils.StringPointer("2006-01-02T15:04:05Z07:00")}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Usage"), Field_id: utils.StringPointer(utils.USAGE), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.USAGE)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("Cost"), Field_id: utils.StringPointer(utils.COST), - Type: utils.StringPointer("cdrfield"), + Type: utils.StringPointer("*composed"), Value: utils.StringPointer(utils.COST)}, } eCfg := map[string]*CdreJsonCfg{ @@ -269,29 +269,29 @@ func TestDfCdreJsonCfgs(t *testing.T) { func TestDfCdrcJsonCfg(t *testing.T) { eFields := []*CdrFieldJsonCfg{} cdrFields := []*CdrFieldJsonCfg{ - &CdrFieldJsonCfg{Tag: utils.StringPointer("tor"), Field_id: utils.StringPointer(utils.TOR), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("tor"), Field_id: utils.StringPointer(utils.TOR), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("2"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("accid"), Field_id: utils.StringPointer(utils.ACCID), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("accid"), Field_id: utils.StringPointer(utils.ACCID), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("3"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("reqtype"), Field_id: utils.StringPointer(utils.REQTYPE), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("reqtype"), Field_id: utils.StringPointer(utils.REQTYPE), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("4"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("direction"), Field_id: utils.StringPointer(utils.DIRECTION), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("direction"), Field_id: utils.StringPointer(utils.DIRECTION), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("5"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("tenant"), Field_id: utils.StringPointer(utils.TENANT), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("tenant"), Field_id: utils.StringPointer(utils.TENANT), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("6"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("category"), Field_id: utils.StringPointer(utils.CATEGORY), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("category"), Field_id: utils.StringPointer(utils.CATEGORY), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("7"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("account"), Field_id: utils.StringPointer(utils.ACCOUNT), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("account"), Field_id: utils.StringPointer(utils.ACCOUNT), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("8"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("subject"), Field_id: utils.StringPointer(utils.SUBJECT), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("subject"), Field_id: utils.StringPointer(utils.SUBJECT), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("9"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("destination"), Field_id: utils.StringPointer(utils.DESTINATION), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("destination"), Field_id: utils.StringPointer(utils.DESTINATION), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("10"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("setup_time"), Field_id: utils.StringPointer(utils.SETUP_TIME), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("setup_time"), Field_id: utils.StringPointer(utils.SETUP_TIME), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("11"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("answer_time"), Field_id: utils.StringPointer(utils.ANSWER_TIME), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("answer_time"), Field_id: utils.StringPointer(utils.ANSWER_TIME), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("12"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("usage"), Field_id: utils.StringPointer(utils.USAGE), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("usage"), Field_id: utils.StringPointer(utils.USAGE), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("13"), Mandatory: utils.BoolPointer(true)}, } eCfg := map[string]*CdrcJsonCfg{ @@ -432,31 +432,31 @@ func TestDiameterAgentJsonCfg(t *testing.T) { Request_filter: utils.StringPointer("Subscription-Id>Subscription-Type(0)"), Continue_on_success: utils.BoolPointer(false), Content_fields: &[]*CdrFieldJsonCfg{ - &CdrFieldJsonCfg{Tag: utils.StringPointer("tor"), Field_id: utils.StringPointer(utils.TOR), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("tor"), Field_id: utils.StringPointer(utils.TOR), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*voice"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("accid"), Field_id: utils.StringPointer(utils.ACCID), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("accid"), Field_id: utils.StringPointer(utils.ACCID), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Session-Id"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("reqtype"), Field_id: utils.StringPointer(utils.REQTYPE), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("reqtype"), Field_id: utils.StringPointer(utils.REQTYPE), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("direction"), Field_id: utils.StringPointer(utils.DIRECTION), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("direction"), Field_id: utils.StringPointer(utils.DIRECTION), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*out"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("tenant"), Field_id: utils.StringPointer(utils.TENANT), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("tenant"), Field_id: utils.StringPointer(utils.TENANT), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("category"), Field_id: utils.StringPointer(utils.CATEGORY), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("category"), Field_id: utils.StringPointer(utils.CATEGORY), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^call_;~Service-Information>IN-Information>Calling-Vlr-Number:s/^$/33000/;~Service-Information>IN-Information>Calling-Vlr-Number:s/^(\\d{5})/${1}/"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("account"), Field_id: utils.StringPointer(utils.ACCOUNT), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("account"), Field_id: utils.StringPointer(utils.ACCOUNT), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("subject"), Field_id: utils.StringPointer(utils.SUBJECT), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("subject"), Field_id: utils.StringPointer(utils.SUBJECT), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("destination"), Field_id: utils.StringPointer(utils.DESTINATION), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("destination"), Field_id: utils.StringPointer(utils.DESTINATION), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Service-Information>IN-Information>Real-Called-Number"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("setup_time"), Field_id: utils.StringPointer(utils.SETUP_TIME), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("setup_time"), Field_id: utils.StringPointer(utils.SETUP_TIME), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Event-Timestamp"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("answer_time"), Field_id: utils.StringPointer(utils.ANSWER_TIME), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("answer_time"), Field_id: utils.StringPointer(utils.ANSWER_TIME), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Event-Timestamp"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("usage"), Field_id: utils.StringPointer(utils.USAGE), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("usage"), Field_id: utils.StringPointer(utils.USAGE), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Requested-Service-Unit>CC-Time"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("subscriber_id"), Field_id: utils.StringPointer("SubscriberId"), Type: utils.StringPointer(utils.CDRFIELD), + &CdrFieldJsonCfg{Tag: utils.StringPointer("subscriber_id"), Field_id: utils.StringPointer("SubscriberId"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Subscription-Id>Subscription-Id-Data"), Mandatory: utils.BoolPointer(true)}, }, }, diff --git a/config/configcdrc_test.go b/config/configcdrc_test.go index f6695530d..7acc357cf 100644 --- a/config/configcdrc_test.go +++ b/config/configcdrc_test.go @@ -51,29 +51,29 @@ func TestLoadCdrcConfigMultipleFiles(t *testing.T) { PartialRecordCache: time.Duration(10) * time.Second, HeaderFields: make([]*CfgCdrField, 0), ContentFields: []*CfgCdrField{ - &CfgCdrField{Tag: "tor", Type: "cdrfield", FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), + &CfgCdrField{Tag: "tor", Type: utils.META_COMPOSED, FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "accid", Type: "cdrfield", FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), + &CfgCdrField{Tag: "accid", Type: utils.META_COMPOSED, FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "reqtype", Type: "cdrfield", FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), + &CfgCdrField{Tag: "reqtype", Type: utils.META_COMPOSED, FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "direction", Type: "cdrfield", FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), + &CfgCdrField{Tag: "direction", Type: utils.META_COMPOSED, FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "tenant", Type: "cdrfield", FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), + &CfgCdrField{Tag: "tenant", Type: utils.META_COMPOSED, FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "category", Type: "cdrfield", FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), + &CfgCdrField{Tag: "category", Type: utils.META_COMPOSED, FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "account", Type: "cdrfield", FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), + &CfgCdrField{Tag: "account", Type: utils.META_COMPOSED, FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "subject", Type: "cdrfield", FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), + &CfgCdrField{Tag: "subject", Type: utils.META_COMPOSED, FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "destination", Type: "cdrfield", FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), + &CfgCdrField{Tag: "destination", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "setup_time", Type: "cdrfield", FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), + &CfgCdrField{Tag: "setup_time", Type: utils.META_COMPOSED, FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "answer_time", Type: "cdrfield", FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), + &CfgCdrField{Tag: "answer_time", Type: utils.META_COMPOSED, FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "usage", Type: "cdrfield", FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), + &CfgCdrField{Tag: "usage", Type: utils.META_COMPOSED, FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, }, TrailerFields: make([]*CfgCdrField, 0), @@ -94,29 +94,29 @@ func TestLoadCdrcConfigMultipleFiles(t *testing.T) { CdrFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), HeaderFields: make([]*CfgCdrField, 0), ContentFields: []*CfgCdrField{ - &CfgCdrField{Tag: "tor", Type: "cdrfield", FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), + &CfgCdrField{Tag: "tor", Type: utils.META_COMPOSED, FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "accid", Type: "cdrfield", FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), + &CfgCdrField{Tag: "accid", Type: utils.META_COMPOSED, FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "reqtype", Type: "cdrfield", FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), + &CfgCdrField{Tag: "reqtype", Type: utils.META_COMPOSED, FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "direction", Type: "cdrfield", FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), + &CfgCdrField{Tag: "direction", Type: utils.META_COMPOSED, FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "tenant", Type: "cdrfield", FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), + &CfgCdrField{Tag: "tenant", Type: utils.META_COMPOSED, FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "category", Type: "cdrfield", FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), + &CfgCdrField{Tag: "category", Type: utils.META_COMPOSED, FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "account", Type: "cdrfield", FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), + &CfgCdrField{Tag: "account", Type: utils.META_COMPOSED, FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "subject", Type: "cdrfield", FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), + &CfgCdrField{Tag: "subject", Type: utils.META_COMPOSED, FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "destination", Type: "cdrfield", FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), + &CfgCdrField{Tag: "destination", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "setup_time", Type: "cdrfield", FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), + &CfgCdrField{Tag: "setup_time", Type: utils.META_COMPOSED, FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "answer_time", Type: "cdrfield", FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), + &CfgCdrField{Tag: "answer_time", Type: utils.META_COMPOSED, FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "usage", Type: "cdrfield", FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), + &CfgCdrField{Tag: "usage", Type: utils.META_COMPOSED, FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, }, TrailerFields: make([]*CfgCdrField, 0), @@ -160,29 +160,29 @@ func TestLoadCdrcConfigMultipleFiles(t *testing.T) { CdrFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), HeaderFields: make([]*CfgCdrField, 0), ContentFields: []*CfgCdrField{ - &CfgCdrField{Tag: "tor", Type: "cdrfield", FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), + &CfgCdrField{Tag: "tor", Type: utils.META_COMPOSED, FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("2", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "accid", Type: "cdrfield", FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), + &CfgCdrField{Tag: "accid", Type: utils.META_COMPOSED, FieldId: utils.ACCID, Value: utils.ParseRSRFieldsMustCompile("3", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "reqtype", Type: "cdrfield", FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), + &CfgCdrField{Tag: "reqtype", Type: utils.META_COMPOSED, FieldId: utils.REQTYPE, Value: utils.ParseRSRFieldsMustCompile("4", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "direction", Type: "cdrfield", FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), + &CfgCdrField{Tag: "direction", Type: utils.META_COMPOSED, FieldId: utils.DIRECTION, Value: utils.ParseRSRFieldsMustCompile("5", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "tenant", Type: "cdrfield", FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), + &CfgCdrField{Tag: "tenant", Type: utils.META_COMPOSED, FieldId: utils.TENANT, Value: utils.ParseRSRFieldsMustCompile("6", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "category", Type: "cdrfield", FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), + &CfgCdrField{Tag: "category", Type: utils.META_COMPOSED, FieldId: utils.CATEGORY, Value: utils.ParseRSRFieldsMustCompile("7", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "account", Type: "cdrfield", FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), + &CfgCdrField{Tag: "account", Type: utils.META_COMPOSED, FieldId: utils.ACCOUNT, Value: utils.ParseRSRFieldsMustCompile("8", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "subject", Type: "cdrfield", FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), + &CfgCdrField{Tag: "subject", Type: utils.META_COMPOSED, FieldId: utils.SUBJECT, Value: utils.ParseRSRFieldsMustCompile("9", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "destination", Type: "cdrfield", FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), + &CfgCdrField{Tag: "destination", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("10", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "setup_time", Type: "cdrfield", FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), + &CfgCdrField{Tag: "setup_time", Type: utils.META_COMPOSED, FieldId: utils.SETUP_TIME, Value: utils.ParseRSRFieldsMustCompile("11", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "answer_time", Type: "cdrfield", FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), + &CfgCdrField{Tag: "answer_time", Type: utils.META_COMPOSED, FieldId: utils.ANSWER_TIME, Value: utils.ParseRSRFieldsMustCompile("12", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, - &CfgCdrField{Tag: "usage", Type: "cdrfield", FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), + &CfgCdrField{Tag: "usage", Type: utils.META_COMPOSED, FieldId: utils.USAGE, Value: utils.ParseRSRFieldsMustCompile("13", utils.INFIELD_SEP), FieldFilter: utils.ParseRSRFieldsMustCompile("", utils.INFIELD_SEP), Width: 0, Strip: "", Padding: "", Layout: "", Mandatory: true}, }, TrailerFields: make([]*CfgCdrField, 0), diff --git a/config/libconfig_json.go b/config/libconfig_json.go index 38860cd9b..fa013c5d1 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -109,7 +109,7 @@ type CdrFieldJsonCfg struct { Tag *string Type *string Field_id *string - Metatag_id *string + Handler_id *string Value *string Width *int Strip *string diff --git a/utils/consts.go b/utils/consts.go index 2a58fc381..89d0febe1 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -30,172 +30,170 @@ var ( ) const ( - VERSION = "0.9.1~rc8" - DIAMETER_FIRMWARE_REVISION = 918 - REDIS_MAX_CONNS = 10 - 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" - META_PREPAID = "*prepaid" - POSTPAID = "postpaid" - META_POSTPAID = "*postpaid" - PSEUDOPREPAID = "pseudoprepaid" - META_PSEUDOPREPAID = "*pseudoprepaid" - META_RATED = "*rated" - META_NONE = "*none" - META_NOW = "*now" - 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_CDR_STATS = "tp_cdr_stats" - 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_TP_DERIVED_CHARGERS = "tp_derived_chargers" - TBL_TP_USERS = "tp_users" - TBL_TP_ALIASES = "tp_aliases" - 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" - USERS_CSV = "Users.csv" - ALIASES_CSV = "Aliases.csv" - ROUNDING_UP = "*up" - ROUNDING_MIDDLE = "*middle" - ROUNDING_DOWN = "*down" - ANY = "*any" - ASAP = "*asap" - USERS = "*users" - COMMENT_CHAR = '#' - CSV_SEP = ',' - FALLBACK_SEP = ';' - INFIELD_SEP = ";" - FIELDS_SEP = "," - STATIC_HDRVAL_SEP = "::" - REGEXP_PREFIX = "~" - FILTER_VAL_START = "(" - FILTER_VAL_END = ")" - JSON = "json" - GOB = "gob" - MSGPACK = "msgpack" - CSV_LOAD = "CSVLOAD" - CGRID = "CgrId" - TOR = "TOR" - ORDERID = "OrderId" - ACCID = "AccId" - CDRHOST = "CdrHost" - CDRSOURCE = "CdrSource" - REQTYPE = "ReqType" - DIRECTION = "Direction" - TENANT = "Tenant" - CATEGORY = "Category" - ACCOUNT = "Account" - SUBJECT = "Subject" - DESTINATION = "Destination" - SETUP_TIME = "SetupTime" - ANSWER_TIME = "AnswerTime" - USAGE = "Usage" - PDD = "Pdd" - SUPPLIER = "Supplier" - MEDI_RUNID = "MediationRunId" - RATED_ACCOUNT = "RatedAccount" - RATED_SUBJECT = "RatedSubject" - COST = "Cost" - COST_DETAILS = "CostDetails" - RATED = "rated" - RATED_FLD = "Rated" - MAX_USAGE = "MaxUsage" - DEFAULT_RUNID = "*default" - META_DEFAULT = "*default" - STATIC_VALUE_PREFIX = "^" - CSV = "csv" - FWV = "fwv" - DRYRUN = "dry_run" - COMBIMED = "combimed" - 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 = ":" - FORKED_CDR = "forked_cdr" - UNIT_TEST = "UNIT_TEST" - HDR_VAL_SEP = "/" - MONETARY = "*monetary" - SMS = "*sms" - GENERIC = "*generic" - DATA = "*data" - VOICE = "*voice" - MAX_COST_FREE = "*free" - MAX_COST_DISCONNECT = "*disconnect" - HOURS = "hours" - MINUTES = "minutes" - NANOSECONDS = "nanoseconds" - SECONDS = "seconds" - OUT = "*out" - IN = "*in" - META_OUT = "*out" - META_ANY = "*any" - CDR_IMPORT = "cdr_import" - CDR_EXPORT = "cdr_export" - CDRFIELD = "cdrfield" - ASR = "ASR" - ACD = "ACD" - FILTER_REGEXP_TPL = "$1$2$3$4$5" - ACTION_PLAN_PREFIX = "apl_" - ACTION_TRIGGER_PREFIX = "atr_" - RATING_PLAN_PREFIX = "rpl_" - RATING_PROFILE_PREFIX = "rpf_" - ACTION_PREFIX = "act_" - SHARED_GROUP_PREFIX = "shg_" - ACCOUNT_PREFIX = "acc_" - DESTINATION_PREFIX = "dst_" - LCR_PREFIX = "lcr_" - DERIVEDCHARGERS_PREFIX = "dcs_" - CDR_STATS_QUEUE_PREFIX = "csq_" - PUBSUB_SUBSCRIBERS_PREFIX = "pss_" - USERS_PREFIX = "usr_" - ALIASES_PREFIX = "als_" - REVERSE_ALIASES_PREFIX = "rls_" - CDR_STATS_PREFIX = "cst_" - 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_" - LOADINST_KEY = "load_history" - // sources + VERSION = "0.9.1~rc8" + DIAMETER_FIRMWARE_REVISION = 918 + REDIS_MAX_CONNS = 10 + 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" + META_PREPAID = "*prepaid" + POSTPAID = "postpaid" + META_POSTPAID = "*postpaid" + PSEUDOPREPAID = "pseudoprepaid" + META_PSEUDOPREPAID = "*pseudoprepaid" + META_RATED = "*rated" + META_NONE = "*none" + META_NOW = "*now" + 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_CDR_STATS = "tp_cdr_stats" + 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_TP_DERIVED_CHARGERS = "tp_derived_chargers" + TBL_TP_USERS = "tp_users" + TBL_TP_ALIASES = "tp_aliases" + 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" + USERS_CSV = "Users.csv" + ALIASES_CSV = "Aliases.csv" + ROUNDING_UP = "*up" + ROUNDING_MIDDLE = "*middle" + ROUNDING_DOWN = "*down" + ANY = "*any" + ASAP = "*asap" + USERS = "*users" + COMMENT_CHAR = '#' + CSV_SEP = ',' + FALLBACK_SEP = ';' + INFIELD_SEP = ";" + FIELDS_SEP = "," + STATIC_HDRVAL_SEP = "::" + REGEXP_PREFIX = "~" + FILTER_VAL_START = "(" + FILTER_VAL_END = ")" + JSON = "json" + GOB = "gob" + MSGPACK = "msgpack" + CSV_LOAD = "CSVLOAD" + CGRID = "CgrId" + TOR = "TOR" + ORDERID = "OrderId" + ACCID = "AccId" + CDRHOST = "CdrHost" + CDRSOURCE = "CdrSource" + REQTYPE = "ReqType" + DIRECTION = "Direction" + TENANT = "Tenant" + CATEGORY = "Category" + ACCOUNT = "Account" + SUBJECT = "Subject" + DESTINATION = "Destination" + SETUP_TIME = "SetupTime" + ANSWER_TIME = "AnswerTime" + USAGE = "Usage" + PDD = "Pdd" + SUPPLIER = "Supplier" + MEDI_RUNID = "MediationRunId" + RATED_ACCOUNT = "RatedAccount" + RATED_SUBJECT = "RatedSubject" + COST = "Cost" + COST_DETAILS = "CostDetails" + RATED = "rated" + RATED_FLD = "Rated" + MAX_USAGE = "MaxUsage" + DEFAULT_RUNID = "*default" + META_DEFAULT = "*default" + STATIC_VALUE_PREFIX = "^" + CSV = "csv" + FWV = "fwv" + DRYRUN = "dry_run" + META_COMBIMED = "*combimed" + 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 = ":" + FORKED_CDR = "forked_cdr" + UNIT_TEST = "UNIT_TEST" + HDR_VAL_SEP = "/" + MONETARY = "*monetary" + SMS = "*sms" + GENERIC = "*generic" + DATA = "*data" + VOICE = "*voice" + MAX_COST_FREE = "*free" + MAX_COST_DISCONNECT = "*disconnect" + HOURS = "hours" + MINUTES = "minutes" + NANOSECONDS = "nanoseconds" + SECONDS = "seconds" + OUT = "*out" + IN = "*in" + META_OUT = "*out" + META_ANY = "*any" + CDR_IMPORT = "cdr_import" + CDR_EXPORT = "cdr_export" + ASR = "ASR" + ACD = "ACD" + FILTER_REGEXP_TPL = "$1$2$3$4$5" + ACTION_PLAN_PREFIX = "apl_" + ACTION_TRIGGER_PREFIX = "atr_" + RATING_PLAN_PREFIX = "rpl_" + RATING_PROFILE_PREFIX = "rpf_" + ACTION_PREFIX = "act_" + SHARED_GROUP_PREFIX = "shg_" + ACCOUNT_PREFIX = "acc_" + DESTINATION_PREFIX = "dst_" + LCR_PREFIX = "lcr_" + DERIVEDCHARGERS_PREFIX = "dcs_" + CDR_STATS_QUEUE_PREFIX = "csq_" + PUBSUB_SUBSCRIBERS_PREFIX = "pss_" + USERS_PREFIX = "usr_" + ALIASES_PREFIX = "als_" + REVERSE_ALIASES_PREFIX = "rls_" + CDR_STATS_PREFIX = "cst_" + 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_" + LOADINST_KEY = "load_history" SESSION_MANAGER_SOURCE = "SMR" MEDIATOR_SOURCE = "MED" CDRS_SOURCE = "CDRS" @@ -205,10 +203,9 @@ const ( CREATE_TARIFFPLAN_TABLES_SQL = "create_tariffplan_tables.sql" TEST_SQL = "TEST_SQL" DESTINATIONS_LOAD_THRESHOLD = 0.1 - CONSTANT = "constant" - FILLER = "filler" - METATAG = "metatag" - HTTP_POST = "http_post" + META_CONSTANT = "*constant" + META_FILLER = "*filler" + META_HANDLER = "*handler" META_HTTP_POST = "*http_post" META_HTTP_JSON = "*http_json" META_HTTP_JSONRPC = "*http_jsonrpc" @@ -261,6 +258,7 @@ const ( TRIGGER_MAX_BALANCE = "*max_balance" TRIGGER_BALANCE_EXPIRED = "*balance_expired" HIERARCHY_SEP = ">" + META_COMPOSED = "*composed" ) var ( From 5e35936b73548be30d378e785986239e214d7f35 Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 26 Nov 2015 14:17:56 +0100 Subject: [PATCH 07/30] Test cost 0 for not authorized destination --- engine/cdrs.go | 3 +++ general_tests/tutorial_local_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/engine/cdrs.go b/engine/cdrs.go index bb91737d9..c1f220e9a 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -341,6 +341,9 @@ func (self *CdrServer) getCostFromRater(storedCdr *StoredCdr) (*CallCost, error) if err = self.rater.Debit(cd, cc); err == nil { // Debit has occured, we are forced to write the log, even if CDR store is disabled self.cdrDb.LogCallCost(storedCdr.CgrId, utils.CDRS_SOURCE, storedCdr.MediationRunId, cc) } + if storedCdr.AccId == "testtutlocal_3" { + utils.Logger.Debug(fmt.Sprintf("### getCostFromRater, cdr %+v, cd: %+v, cc: %+v, error: %v", storedCdr, cd, cc, err)) + } } else { err = self.rater.GetCost(cd, cc) } diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index ae325382c..adaf98472 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -644,6 +644,31 @@ func TestTutLocalCostErrors(t *testing.T) { t.Errorf("Unexpected Cost for Cdr received: %+v", cdrs[0]) } } + cdr3 := &engine.ExternalCdr{TOR: utils.VOICE, + AccId: "testtutlocal_3", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_POSTPAID, Direction: utils.OUT, + Tenant: "cgrates.org", Category: "fake", Account: "1001", Subject: "1001", Destination: "2002", Supplier: "SUPPL1", + SetupTime: "2014-08-04T13:00:00Z", AnswerTime: "2014-08-04T13:00:07Z", + Usage: "1", Pdd: "7.0", ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, + } + if err := tutLocalRpc.Call("CdrsV2.ProcessExternalCdr", cdr3, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for CDR to be processed + req = utils.RpcCdrsFilter{RunIds: []string{utils.META_DEFAULT}, Accounts: []string{cdr3.Account}, DestPrefixes: []string{cdr3.Destination}} + if err := tutLocalRpc.Call("ApierV2.GetCdrs", req, &cdrs); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if len(cdrs) != 1 { + t.Error("Unexpected number of CDRs returned: ", len(cdrs)) + } else { + if cdrs[0].AccId != cdr3.AccId { + t.Errorf("Unexpected AccId for Cdr received: %+v", cdrs[0]) + } + if cdrs[0].Cost != -1 { + t.Errorf("Unexpected Cost for Cdr received: %+v", cdrs[0]) + } + } } // Make sure queueids were created From e0de907f8801f45b7923a426d6f8d0b16eb65d0e Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 26 Nov 2015 17:34:00 +0200 Subject: [PATCH 08/30] fix error propagation --- engine/calldesc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 399e96442..b98e3def8 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -676,13 +676,13 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { utils.Logger.Err(fmt.Sprintf("Account: %s, not found", cd.GetAccountKey())) return nil, utils.ErrAccountNotFound } else { - if memberIds, err := account.GetUniqueSharedGroupMembers(cd); err == nil { + if memberIds, sgerr := account.GetUniqueSharedGroupMembers(cd); sgerr == nil { _, err = Guardian.Guard(func() (interface{}, error) { cc, err = cd.debit(account, false, true) return 0, err }, 0, memberIds...) } else { - return nil, err + return nil, sgerr } return cc, err } From 88c5364f60481e04e1ef386576c96d5d182cdf84 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 26 Nov 2015 17:35:22 +0200 Subject: [PATCH 09/30] removed log --- engine/cdrs.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/engine/cdrs.go b/engine/cdrs.go index c1f220e9a..bb91737d9 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -341,9 +341,6 @@ func (self *CdrServer) getCostFromRater(storedCdr *StoredCdr) (*CallCost, error) if err = self.rater.Debit(cd, cc); err == nil { // Debit has occured, we are forced to write the log, even if CDR store is disabled self.cdrDb.LogCallCost(storedCdr.CgrId, utils.CDRS_SOURCE, storedCdr.MediationRunId, cc) } - if storedCdr.AccId == "testtutlocal_3" { - utils.Logger.Debug(fmt.Sprintf("### getCostFromRater, cdr %+v, cd: %+v, cc: %+v, error: %v", storedCdr, cd, cc, err)) - } } else { err = self.rater.GetCost(cd, cc) } From 0b6d78d714aa38011cdefd7e76fd378bc018986e Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 26 Nov 2015 17:49:56 +0100 Subject: [PATCH 10/30] CDR processing benchmarks attached to local tests in apier/v2/cdrs_* --- apier/v2/cdrs_mysql_local_test.go | 77 ++++++++++++++++++++++++++++ apier/v2/cdrs_psql_local_test.go | 77 ++++++++++++++++++++++++++++ engine/cdrs_local_test.go | 1 - general_tests/tutorial_local_test.go | 3 -- 4 files changed, 154 insertions(+), 4 deletions(-) diff --git a/apier/v2/cdrs_mysql_local_test.go b/apier/v2/cdrs_mysql_local_test.go index 5b5444973..bcbb6c106 100644 --- a/apier/v2/cdrs_mysql_local_test.go +++ b/apier/v2/cdrs_mysql_local_test.go @@ -23,6 +23,7 @@ import ( "net/rpc" "net/rpc/jsonrpc" "path" + "strconv" "testing" "time" @@ -304,6 +305,82 @@ func TestV2CdrsMysqlRateWithTP(t *testing.T) { } } +// Benchmark speed of processing 1000 CDRs +func TestV2CdrsMysqlProcessRatedExternalCdrBenchmark(t *testing.T) { + if !*testLocal { + return + } + cdr := &engine.ExternalCdr{TOR: utils.VOICE, + AccId: "benchratedcdr", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: utils.OUT, + Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1001", Supplier: "SUPPL1", + SetupTime: "2014-08-04T13:00:00Z", AnswerTime: "2014-08-04T13:00:07Z", + Usage: "15", Pdd: "7.0", ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, + } + var reply string + tStart := time.Now() + nrCdrs := 1000 + for i := 0; i < nrCdrs; i++ { + cdr.AccId = "benchratedcdr" + strconv.Itoa(i) + if err := cdrsRpc.Call("CdrsV2.ProcessExternalCdr", cdr, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + } + if durExec := time.Now().Sub(tStart); durExec > time.Duration(1)*time.Second { + t.Errorf("Processing of %d rated CDRs took: %v", nrCdrs, durExec) + } +} + +// Benchmark speed of re-rating 1000 CDRs +func TestV2CdrsMysqlReRateWithTPBenchmark(t *testing.T) { + if !*testLocal { + return + } + var nrCdrs int64 + req := utils.AttrRateCdrs{RerateRated: true, RerateErrors: true} + if err := cdrsRpc.Call("ApierV2.CountCdrs", req, &nrCdrs); err != nil { + t.Error("Unexpected error: ", err.Error()) + } + tStart := time.Now() + var reply string + if err := cdrsRpc.Call("CdrsV2.RateCdrs", req, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + if durExec := time.Now().Sub(tStart); durExec > time.Duration(1)*time.Second { + t.Errorf("Rerating of %d rated CDRs took: %v", nrCdrs, durExec) + } +} + +// Benchmark speed of processing 1000 postpaid CDRs +func TestV2CdrsMysqlProcessPostpaidExternalCdrBenchmark(t *testing.T) { + if !*testLocal { + return + } + cdr := &engine.ExternalCdr{TOR: utils.VOICE, + AccId: "benchpostpaidcdr", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_POSTPAID, Direction: utils.OUT, + Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", + SetupTime: "2014-08-04T13:00:00Z", AnswerTime: "2014-08-04T13:00:07Z", + Usage: "15", Pdd: "7.0", ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, + } + var reply string + tStart := time.Now() + nrCdrs := 1000 + for i := 0; i < nrCdrs; i++ { + cdr.AccId = "benchpostpaidcdr" + strconv.Itoa(i) + if err := cdrsRpc.Call("CdrsV2.ProcessExternalCdr", cdr, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + } + if durExec := time.Now().Sub(tStart); durExec > time.Duration(1)*time.Second { + t.Errorf("Processing of %d postpaid CDRs took: %v", nrCdrs, durExec) + } +} + func TestV2CdrsMysqlKillEngine(t *testing.T) { if !*testLocal { return diff --git a/apier/v2/cdrs_psql_local_test.go b/apier/v2/cdrs_psql_local_test.go index be4897115..dc5d6e059 100644 --- a/apier/v2/cdrs_psql_local_test.go +++ b/apier/v2/cdrs_psql_local_test.go @@ -23,6 +23,7 @@ import ( "net/rpc/jsonrpc" "os/exec" "path" + "strconv" "testing" "time" @@ -302,6 +303,82 @@ func TestV2CdrsPsqlRateWithTP(t *testing.T) { } } +// Benchmark speed of processing 1000 CDRs +func TestV2CdrsPsqlProcessRatedExternalCdrBenchmark(t *testing.T) { + if !*testLocal { + return + } + cdr := &engine.ExternalCdr{TOR: utils.VOICE, + AccId: "benchratedcdr", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: utils.OUT, + Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1001", Supplier: "SUPPL1", + SetupTime: "2014-08-04T13:00:00Z", AnswerTime: "2014-08-04T13:00:07Z", + Usage: "15", Pdd: "7.0", ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, + } + var reply string + tStart := time.Now() + nrCdrs := 1000 + for i := 0; i < nrCdrs; i++ { + cdr.AccId = "benchratedcdr" + strconv.Itoa(i) + if err := cdrsPsqlRpc.Call("CdrsV2.ProcessExternalCdr", cdr, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + } + if durExec := time.Now().Sub(tStart); durExec > time.Duration(1)*time.Second { + t.Errorf("Processing of %d rated CDRs took: %v", nrCdrs, durExec) + } +} + +// Benchmark speed of re-rating 1000 CDRs +func TestV2CdrsPsqlReRateWithTPBenchmark(t *testing.T) { + if !*testLocal { + return + } + var nrCdrs int64 + req := utils.AttrRateCdrs{RerateRated: true, RerateErrors: true} + if err := cdrsPsqlRpc.Call("ApierV2.CountCdrs", req, &nrCdrs); err != nil { + t.Error("Unexpected error: ", err.Error()) + } + tStart := time.Now() + var reply string + if err := cdrsPsqlRpc.Call("CdrsV2.RateCdrs", req, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + if durExec := time.Now().Sub(tStart); durExec > time.Duration(1)*time.Second { + t.Errorf("Rerating of %d rated CDRs took: %v", nrCdrs, durExec) + } +} + +// Benchmark speed of processing 1000 postpaid CDRs +func TestV2CdrsPsqlProcessPostpaidExternalCdrBenchmark(t *testing.T) { + if !*testLocal { + return + } + cdr := &engine.ExternalCdr{TOR: utils.VOICE, + AccId: "benchpostpaidcdr", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_POSTPAID, Direction: utils.OUT, + Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", + SetupTime: "2014-08-04T13:00:00Z", AnswerTime: "2014-08-04T13:00:07Z", + Usage: "15", Pdd: "7.0", ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, + } + var reply string + tStart := time.Now() + nrCdrs := 1000 + for i := 0; i < nrCdrs; i++ { + cdr.AccId = "benchpostpaidcdr" + strconv.Itoa(i) + if err := cdrsPsqlRpc.Call("CdrsV2.ProcessExternalCdr", cdr, &reply); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if reply != utils.OK { + t.Error("Unexpected reply received: ", reply) + } + } + if durExec := time.Now().Sub(tStart); durExec > time.Duration(1)*time.Second { + t.Errorf("Processing of %d postpaid CDRs took: %v", nrCdrs, durExec) + } +} + func TestV2CdrsPsqlKillEngine(t *testing.T) { if !*testLocal { return diff --git a/engine/cdrs_local_test.go b/engine/cdrs_local_test.go index 4a710e855..7c1e631dd 100644 --- a/engine/cdrs_local_test.go +++ b/engine/cdrs_local_test.go @@ -20,7 +20,6 @@ package engine import ( "path" - //"reflect" "testing" "time" diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index adaf98472..434fa6461 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -21,14 +21,11 @@ package general_tests import ( "net/rpc" "net/rpc/jsonrpc" - //"os" "path" "reflect" - //"strings" "testing" "time" - //"github.com/cgrates/cgrates/apier/v1" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" From 873ea6721908c653e3ff50aee1e0ce7cbef52d6c Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 27 Nov 2015 14:35:44 +0100 Subject: [PATCH 11/30] Diameter request processors with field filters --- agents/dmtagent.go | 44 +++++++++++++++++++------ agents/libdmt.go | 23 +++++++++++-- config/config_defaults.go | 2 +- config/config_json_test.go | 2 +- data/conf/samples/dmtagent/cgrates.json | 30 ++++++++--------- 5 files changed, 71 insertions(+), 30 deletions(-) diff --git a/agents/dmtagent.go b/agents/dmtagent.go index 3e034bf97..2f5c54cfc 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -65,19 +65,44 @@ func (self *DiameterAgent) handlers() diam.Handler { return dSM } -func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { - //utils.Logger.Warning(fmt.Sprintf(" Received CCR message from %s:\n%s", c.RemoteAddr(), m)) - var ccr CCR - if err := m.Unmarshal(&ccr); err != nil { - utils.Logger.Err(fmt.Sprintf(" Unmarshaling message: %s, error: %s", m, err)) - return - } - ccr.diamMessage = m // Save it for later searches inside AVPs - cca := NewCCAFromCCR(&ccr) +func (self *DiameterAgent) processCCR(ccr *CCR) (*CCA, error) { + cca := NewCCAFromCCR(ccr) cca.OriginHost = self.cgrCfg.DiameterAgentCfg().OriginHost cca.OriginRealm = self.cgrCfg.DiameterAgentCfg().OriginRealm cca.GrantedServiceUnit.CCTime = 300 cca.ResultCode = diam.Success + return cca, nil +} + +func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { + ccr, err := NewCCRFromDiameterMessage(m) + if err != nil { + utils.Logger.Err(fmt.Sprintf(" Unmarshaling message: %s, error: %s", m, err)) + return + } + var cca *CCA // For now we simply overload in loop, maybe we will find some other use of this + for _, reqProcessor := range self.cgrCfg.DiameterAgentCfg().RequestProcessors { + passesAllFilters := true + for _, fldFilter := range reqProcessor.RequestFilter { + if !ccr.passesFieldFilter(fldFilter) { + passesAllFilters = false + } + } + if !passesAllFilters { // Not going with this processor further + continue + } + cca, err = self.processCCR(ccr) + if !reqProcessor.ContinueOnSuccess { + break + } + } + if err != nil { + utils.Logger.Err(fmt.Sprintf(" Failed to generate CCA, error: %s", err.Error())) + return + } else if cca == nil { + utils.Logger.Err(fmt.Sprintf(" No request processor enabled for CCR: %+v, ignoring request", ccr)) + return + } if dmtA, err := cca.AsDiameterMessage(); err != nil { utils.Logger.Err(fmt.Sprintf(" Failed to convert cca as diameter message, error: %s", err.Error())) return @@ -85,7 +110,6 @@ func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { utils.Logger.Err(fmt.Sprintf(" Failed to write message to %s: %s\n%s\n", c.RemoteAddr(), err, dmtA)) return } - } func (self *DiameterAgent) handleALL(c diam.Conn, m *diam.Message) { diff --git a/agents/libdmt.go b/agents/libdmt.go index 5f0aaa5f3..5ac2c0951 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -143,6 +143,15 @@ func storedCdrToCCR(cdr *engine.StoredCdr, originHost, originRealm string, vendo return ccr } +func NewCCRFromDiameterMessage(m *diam.Message) (*CCR, error) { + var ccr CCR + if err := m.Unmarshal(&ccr); err != nil { + return nil, err + } + ccr.diamMessage = m + return &ccr, nil +} + // CallControl Request type CCR struct { SessionId string `avp:"Session-Id"` @@ -332,6 +341,14 @@ func avpValAsString(a *diam.AVP) string { return dataVal[startIdx+1 : endIdx] } +// Follows the implementation in the StorCdr +func (self *CCR) passesFieldFilter(fieldFilter *utils.RSRField) bool { + if fieldFilter == nil { + return true + } + return fieldFilter.FilterPasses(self.eventFieldValue(utils.RSRFields{fieldFilter})) +} + // Handler for meta functions func (self *CCR) metaHandler(tag, arg string) (string, error) { switch tag { @@ -342,9 +359,9 @@ func (self *CCR) metaHandler(tag, arg string) (string, error) { return "", nil } -func (self *CCR) eventFieldValue(cfgFld *config.CfgCdrField) string { +func (self *CCR) eventFieldValue(fldTpl utils.RSRFields) string { var outVal string - for _, rsrTpl := range cfgFld.Value { + for _, rsrTpl := range fldTpl { if rsrTpl.IsStatic() { outVal += rsrTpl.ParseValue("") } else { @@ -387,7 +404,7 @@ func (self *CCR) AsSMGenericEvent(cfgFlds []*config.CfgCdrField) (sessionmanager utils.Logger.Warning(fmt.Sprintf(" Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error())) } case utils.META_COMPOSED: - outVal = self.eventFieldValue(cfgFld) + outVal = self.eventFieldValue(cfgFld.Value) } fmtOut := outVal if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { diff --git a/config/config_defaults.go b/config/config_defaults.go index 522e564f9..53753034e 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -273,7 +273,7 @@ const CGRATES_CFG_JSON = ` { "id": "*default", // formal identifier of this processor "dry_run": false, // do not send the CDRs to CDRS, just parse them - "request_filter": "Subscription-Id>Subscription-Type(0)", // filter requests processed by this processor + "request_filter": "Subscription-Id>Subscription-Id-Type(0)", // filter requests processed by this processor "continue_on_success": false, // continue to the next template if executed "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value {"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "^*voice", "mandatory": true}, diff --git a/config/config_json_test.go b/config/config_json_test.go index 39cdfd5b5..2ab9edcfa 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -429,7 +429,7 @@ func TestDiameterAgentJsonCfg(t *testing.T) { &DARequestProcessorJsnCfg{ Id: utils.StringPointer("*default"), Dry_run: utils.BoolPointer(false), - Request_filter: utils.StringPointer("Subscription-Id>Subscription-Type(0)"), + Request_filter: utils.StringPointer("Subscription-Id>Subscription-Id-Type(0)"), Continue_on_success: utils.BoolPointer(false), Content_fields: &[]*CdrFieldJsonCfg{ &CdrFieldJsonCfg{Tag: utils.StringPointer("tor"), Field_id: utils.StringPointer(utils.TOR), Type: utils.StringPointer(utils.META_COMPOSED), diff --git a/data/conf/samples/dmtagent/cgrates.json b/data/conf/samples/dmtagent/cgrates.json index 5a71f17fb..210a91b55 100644 --- a/data/conf/samples/dmtagent/cgrates.json +++ b/data/conf/samples/dmtagent/cgrates.json @@ -57,24 +57,24 @@ //"dictionaries_dir": "", // path towards directory holding additional dictionaries to load "request_processors": [ { - "id": "*default", // Identifier of this processor + "id": "*default", // formal identifier of this processor "dry_run": false, // do not send the CDRs to CDRS, just parse them - "request_filter": "Subscription-Id>Subscription-Type(0)", // filter requests processed by this processor + "request_filter": "Subscription-Id>Subscription-Id-Type(0)", // filter requests processed by this processor "continue_on_success": false, // continue to the next template if executed "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value - {"tag": "tor", "cdr_field_id": "TOR", "type": "cdrfield", "value": "^*voice", "mandatory": true}, - {"tag": "accid", "cdr_field_id": "AccId", "type": "cdrfield", "value": "Session-Id", "mandatory": true}, - {"tag": "reqtype", "cdr_field_id": "ReqType", "type": "cdrfield", "value": "^*users", "mandatory": true}, - {"tag": "direction", "cdr_field_id": "Direction", "type": "cdrfield", "value": "^*out", "mandatory": true}, - {"tag": "tenant", "cdr_field_id": "Tenant", "type": "cdrfield", "value": "^*users", "mandatory": true}, - {"tag": "category", "cdr_field_id": "Category", "type": "cdrfield", "value": "^call_;~Calling-Vlr-Number:s/^$/33000/;~Calling-Vlr-Number:s/^(\\d{5})/${1}/", "mandatory": true}, - {"tag": "account", "cdr_field_id": "Account", "type": "cdrfield", "value": "^*users", "mandatory": true}, - {"tag": "subject", "cdr_field_id": "Subject", "type": "cdrfield", "value": "^*users", "mandatory": true}, - {"tag": "destination", "cdr_field_id": "Destination", "type": "cdrfield", "value": "Real-Called-Number", "mandatory": true}, - {"tag": "setup_time", "cdr_field_id": "SetupTime", "type": "cdrfield", "value": "Event-Time", "mandatory": true}, - {"tag": "answer_time", "cdr_field_id": "AnswerTime", "type": "cdrfield", "value": "Event-Time", "mandatory": true}, - {"tag": "usage", "cdr_field_id": "Usage", "type": "cdrfield", "value": "CC-Time", "mandatory": true}, - {"tag": "subscriber_id", "cdr_field_id": "SubscriberId", "type": "cdrfield", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, + {"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "^*voice", "mandatory": true}, + {"tag": "accid", "field_id": "AccId", "type": "*composed", "value": "Session-Id", "mandatory": true}, + {"tag": "reqtype", "field_id": "ReqType", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, + {"tag": "tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "category", "field_id": "Category", "type": "*composed", "value": "^call_;~Service-Information>IN-Information>Calling-Vlr-Number:s/^$/33000/;~Service-Information>IN-Information>Calling-Vlr-Number:s/^(\\d{5})/${1}/", "mandatory": true}, + {"tag": "account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, + {"tag": "setup_time", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "answer_time", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "usage", "field_id": "Usage", "type": "*composed", "value": "Requested-Service-Unit>CC-Time", "mandatory": true}, + {"tag": "subscriber_id", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, ], }, ], From 6e0cb79349df84bcb0d31938920c48a4b71dfd6e Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 27 Nov 2015 14:43:15 +0100 Subject: [PATCH 12/30] Update cgrates.json with defaults --- data/conf/cgrates/cgrates.json | 93 ++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/data/conf/cgrates/cgrates.json b/data/conf/cgrates/cgrates.json index 97f9891f6..e35dd57c7 100644 --- a/data/conf/cgrates/cgrates.json +++ b/data/conf/cgrates/cgrates.json @@ -119,21 +119,21 @@ // "export_dir": "/var/log/cgrates/cdre", // path where the exported CDRs will be placed // "header_fields": [], // template of the exported header fields // "content_fields": [ // template of the exported content fields -// {"tag": "CgrId", "cdr_field_id": "CgrId", "type": "cdrfield", "value": "CgrId"}, -// {"tag":"RunId", "cdr_field_id": "MediationRunId", "type": "cdrfield", "value": "MediationRunId"}, -// {"tag":"Tor", "cdr_field_id": "TOR", "type": "cdrfield", "value": "TOR"}, -// {"tag":"AccId", "cdr_field_id": "AccId", "type": "cdrfield", "value": "AccId"}, -// {"tag":"ReqType", "cdr_field_id": "ReqType", "type": "cdrfield", "value": "ReqType"}, -// {"tag":"Direction", "cdr_field_id": "Direction", "type": "cdrfield", "value": "Direction"}, -// {"tag":"Tenant", "cdr_field_id": "Tenant", "type": "cdrfield", "value": "Tenant"}, -// {"tag":"Category", "cdr_field_id": "Category", "type": "cdrfield", "value": "Category"}, -// {"tag":"Account", "cdr_field_id": "Account", "type": "cdrfield", "value": "Account"}, -// {"tag":"Subject", "cdr_field_id": "Subject", "type": "cdrfield", "value": "Subject"}, -// {"tag":"Destination", "cdr_field_id": "Destination", "type": "cdrfield", "value": "Destination"}, -// {"tag":"SetupTime", "cdr_field_id": "SetupTime", "type": "cdrfield", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00"}, -// {"tag":"AnswerTime", "cdr_field_id": "AnswerTime", "type": "cdrfield", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"}, -// {"tag":"Usage", "cdr_field_id": "Usage", "type": "cdrfield", "value": "Usage"}, -// {"tag":"Cost", "cdr_field_id": "Cost", "type": "cdrfield", "value": "Cost"}, +// {"tag": "CgrId", "field_id": "CgrId", "type": "*composed", "value": "CgrId"}, +// {"tag":"RunId", "field_id": "MediationRunId", "type": "*composed", "value": "MediationRunId"}, +// {"tag":"Tor", "field_id": "TOR", "type": "*composed", "value": "TOR"}, +// {"tag":"AccId", "field_id": "AccId", "type": "*composed", "value": "AccId"}, +// {"tag":"ReqType", "field_id": "ReqType", "type": "*composed", "value": "ReqType"}, +// {"tag":"Direction", "field_id": "Direction", "type": "*composed", "value": "Direction"}, +// {"tag":"Tenant", "field_id": "Tenant", "type": "*composed", "value": "Tenant"}, +// {"tag":"Category", "field_id": "Category", "type": "*composed", "value": "Category"}, +// {"tag":"Account", "field_id": "Account", "type": "*composed", "value": "Account"}, +// {"tag":"Subject", "field_id": "Subject", "type": "*composed", "value": "Subject"}, +// {"tag":"Destination", "field_id": "Destination", "type": "*composed", "value": "Destination"}, +// {"tag":"SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00"}, +// {"tag":"AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"}, +// {"tag":"Usage", "field_id": "Usage", "type": "*composed", "value": "Usage"}, +// {"tag":"Cost", "field_id": "Cost", "type": "*composed", "value": "Cost"}, // ], // "trailer_fields": [], // template of the exported trailer fields // } @@ -156,21 +156,22 @@ // "failed_calls_prefix": "missed_calls", // used in case of flatstore CDRs to avoid searching for BYE records // "cdr_source_id": "freeswitch_csv", // free form field, tag identifying the source of the CDRs within CDRS database // "cdr_filter": "", // filter CDR records to import +// "continue_on_success": false, // continue to the next template if executed // "partial_record_cache": "10s", // duration to cache partial records when not pairing // "header_fields": [], // template of the import header fields // "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value -// {"tag": "tor", "cdr_field_id": "TOR", "type": "cdrfield", "value": "2", "mandatory": true}, -// {"tag": "accid", "cdr_field_id": "AccId", "type": "cdrfield", "value": "3", "mandatory": true}, -// {"tag": "reqtype", "cdr_field_id": "ReqType", "type": "cdrfield", "value": "4", "mandatory": true}, -// {"tag": "direction", "cdr_field_id": "Direction", "type": "cdrfield", "value": "5", "mandatory": true}, -// {"tag": "tenant", "cdr_field_id": "Tenant", "type": "cdrfield", "value": "6", "mandatory": true}, -// {"tag": "category", "cdr_field_id": "Category", "type": "cdrfield", "value": "7", "mandatory": true}, -// {"tag": "account", "cdr_field_id": "Account", "type": "cdrfield", "value": "8", "mandatory": true}, -// {"tag": "subject", "cdr_field_id": "Subject", "type": "cdrfield", "value": "9", "mandatory": true}, -// {"tag": "destination", "cdr_field_id": "Destination", "type": "cdrfield", "value": "10", "mandatory": true}, -// {"tag": "setup_time", "cdr_field_id": "SetupTime", "type": "cdrfield", "value": "11", "mandatory": true}, -// {"tag": "answer_time", "cdr_field_id": "AnswerTime", "type": "cdrfield", "value": "12", "mandatory": true}, -// {"tag": "usage", "cdr_field_id": "Usage", "type": "cdrfield", "value": "13", "mandatory": true}, +// {"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "2", "mandatory": true}, +// {"tag": "accid", "field_id": "AccId", "type": "*composed", "value": "3", "mandatory": true}, +// {"tag": "reqtype", "field_id": "ReqType", "type": "*composed", "value": "4", "mandatory": true}, +// {"tag": "direction", "field_id": "Direction", "type": "*composed", "value": "5", "mandatory": true}, +// {"tag": "tenant", "field_id": "Tenant", "type": "*composed", "value": "6", "mandatory": true}, +// {"tag": "category", "field_id": "Category", "type": "*composed", "value": "7", "mandatory": true}, +// {"tag": "account", "field_id": "Account", "type": "*composed", "value": "8", "mandatory": true}, +// {"tag": "subject", "field_id": "Subject", "type": "*composed", "value": "9", "mandatory": true}, +// {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "10", "mandatory": true}, +// {"tag": "setup_time", "field_id": "SetupTime", "type": "*composed", "value": "11", "mandatory": true}, +// {"tag": "answer_time", "field_id": "AnswerTime", "type": "*composed", "value": "12", "mandatory": true}, +// {"tag": "usage", "field_id": "Usage", "type": "*composed", "value": "13", "mandatory": true}, // ], // "trailer_fields": [], // template of the import trailer fields // } @@ -181,7 +182,7 @@ // "listen_bijson": "127.0.0.1:2014", // address where to listen for bidirectional JSON-RPC requests // "rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013> // "cdrs": "internal", // address where to reach CDR Server <""|internal|x.y.z.y:1234> -// "debit_interval": "10s", // interval to perform debits on. +// "debit_interval": "0s", // interval to perform debits on. // "min_call_duration": "0s", // only authorize calls with allowed duration higher than this // "max_call_duration": "3h", // maximum call duration a prepaid call can last //}, @@ -237,6 +238,42 @@ //}, +//"diameter_agent": { +// "enabled": false, // enables the diameter agent: +// "listen": "127.0.0.1:3868", // address where to listen for diameter requests +// "dictionaries_dir": "/usr/share/cgrates/diameter/dict/", // path towards directory holding additional dictionaries to load +// "sm_generic": "internal", // connection towards SMG component for session management +// "timezone": "", // timezone for timestamps where not specified, empty for general defaults <""|UTC|Local|$IANA_TZ_DB> +// "origin_host": "CGR-DA", // diameter Origin-Host AVP used in replies +// "origin_realm": "cgrates.org", // diameter Origin-Realm AVP used in replies +// "vendor_id": 0, // diameter Vendor-Id AVP used in replies +// "product_name": "CGRateS", // diameter Product-Name AVP used in replies +// "request_processors": [ +// { +// "id": "*default", // formal identifier of this processor +// "dry_run": false, // do not send the CDRs to CDRS, just parse them +// "request_filter": "Subscription-Id>Subscription-Id-Type(0)", // filter requests processed by this processor +// "continue_on_success": false, // continue to the next template if executed +// "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value +// {"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "^*voice", "mandatory": true}, +// {"tag": "accid", "field_id": "AccId", "type": "*composed", "value": "Session-Id", "mandatory": true}, +// {"tag": "reqtype", "field_id": "ReqType", "type": "*composed", "value": "^*users", "mandatory": true}, +// {"tag": "direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, +// {"tag": "tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, +// {"tag": "category", "field_id": "Category", "type": "*composed", "value": "^call_;~Service-Information>IN-Information>Calling-Vlr-Number:s/^$/33000/;~Service-Information>IN-Information>Calling-Vlr-Number:s/^(\\d{5})/${1}/", "mandatory": true}, +// {"tag": "account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, +// {"tag": "subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, +// {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, +// {"tag": "setup_time", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, +// {"tag": "answer_time", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, +// {"tag": "usage", "field_id": "Usage", "type": "*composed", "value": "Requested-Service-Unit>CC-Time", "mandatory": true}, +// {"tag": "subscriber_id", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, +// ], +// }, +// ], +//}, + + //"historys": { // "enabled": false, // starts History service: . // "history_dir": "/var/log/cgrates/history", // location on disk where to store history files. From 7bcd168a01b70b5f60f3ad19eb08e89496639e6f Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 27 Nov 2015 17:34:41 +0100 Subject: [PATCH 13/30] Diameter processCCR with remote SMG queries --- agents/dmtagent.go | 54 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/agents/dmtagent.go b/agents/dmtagent.go index 2f5c54cfc..73d68b394 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -65,7 +65,10 @@ func (self *DiameterAgent) handlers() diam.Handler { return dSM } -func (self *DiameterAgent) processCCR(ccr *CCR) (*CCA, error) { +/* + case 1: // Initial credit control + self.smg.Call("SMGenericV1.SessionStart",ev sessionmanager.SMGenericEvent, maxUsage *float64)") + } cca := NewCCAFromCCR(ccr) cca.OriginHost = self.cgrCfg.DiameterAgentCfg().OriginHost cca.OriginRealm = self.cgrCfg.DiameterAgentCfg().OriginRealm @@ -73,6 +76,39 @@ func (self *DiameterAgent) processCCR(ccr *CCR) (*CCA, error) { cca.ResultCode = diam.Success return cca, nil } +*/ + +func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProcessor) (*CCA, error) { + passesAllFilters := true + for _, fldFilter := range reqProcessor.RequestFilter { + if !ccr.passesFieldFilter(fldFilter) { + passesAllFilters = false + } + } + if !passesAllFilters { // Not going with this processor further + return nil, nil + } + smgEv, err := ccr.AsSMGenericEvent(reqProcessor.ContentFields) + if err != nil { + return nil, err + } + var maxUsage float64 + switch ccr.CCRequestType { + case 1: + err = self.smg.Call("SMGenericV1.SessionStart", smgEv, &maxUsage) + case 2: + err = self.smg.Call("SMGenericV1.SessionUpdate", smgEv, &maxUsage) + case 3: + var rpl string + err = self.smg.Call("SMGenericV1.SessionEnd", smgEv, &rpl) + } + cca := NewCCAFromCCR(ccr) + cca.OriginHost = self.cgrCfg.DiameterAgentCfg().OriginHost + cca.OriginRealm = self.cgrCfg.DiameterAgentCfg().OriginRealm + cca.GrantedServiceUnit.CCTime = int(maxUsage) + cca.ResultCode = diam.Success + return cca, nil +} func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { ccr, err := NewCCRFromDiameterMessage(m) @@ -82,22 +118,14 @@ func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { } var cca *CCA // For now we simply overload in loop, maybe we will find some other use of this for _, reqProcessor := range self.cgrCfg.DiameterAgentCfg().RequestProcessors { - passesAllFilters := true - for _, fldFilter := range reqProcessor.RequestFilter { - if !ccr.passesFieldFilter(fldFilter) { - passesAllFilters = false - } + if cca, err = self.processCCR(ccr, reqProcessor); err != nil { + utils.Logger.Err(fmt.Sprintf(" Error processing CCR %+v, processor id: %s, error: %s", ccr, reqProcessor.Id, err.Error())) } - if !passesAllFilters { // Not going with this processor further - continue - } - cca, err = self.processCCR(ccr) - if !reqProcessor.ContinueOnSuccess { + if cca != nil && !reqProcessor.ContinueOnSuccess { break } } - if err != nil { - utils.Logger.Err(fmt.Sprintf(" Failed to generate CCA, error: %s", err.Error())) + if err != nil { //ToDo: return standard diameter error return } else if cca == nil { utils.Logger.Err(fmt.Sprintf(" No request processor enabled for CCR: %+v, ignoring request", ccr)) From d3c326c372ca05242e3794b5be2ec92ea8f5456c Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 27 Nov 2015 20:52:43 +0100 Subject: [PATCH 14/30] Fix AddAccountAliases and AddRatingSubjectAliases APIs, fix deadlock in updateAlias --- agents/dmtagent.go | 13 ------------ apier/v1/aliases.go | 34 ++++++++++++++++++++++--------- apier/v2/cdrs_mysql_local_test.go | 3 ++- apier/v2/cdrs_psql_local_test.go | 3 ++- engine/aliases.go | 4 ++-- engine/cdrs.go | 16 +++++++++++++++ 6 files changed, 46 insertions(+), 27 deletions(-) diff --git a/agents/dmtagent.go b/agents/dmtagent.go index 73d68b394..d8e227da1 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -65,19 +65,6 @@ func (self *DiameterAgent) handlers() diam.Handler { return dSM } -/* - case 1: // Initial credit control - self.smg.Call("SMGenericV1.SessionStart",ev sessionmanager.SMGenericEvent, maxUsage *float64)") - } - cca := NewCCAFromCCR(ccr) - cca.OriginHost = self.cgrCfg.DiameterAgentCfg().OriginHost - cca.OriginRealm = self.cgrCfg.DiameterAgentCfg().OriginRealm - cca.GrantedServiceUnit.CCTime = 300 - cca.ResultCode = diam.Success - return cca, nil -} -*/ - func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProcessor) (*CCA, error) { passesAllFilters := true for _, fldFilter := range reqProcessor.RequestFilter { diff --git a/apier/v1/aliases.go b/apier/v1/aliases.go index e17798433..531f97e14 100644 --- a/apier/v1/aliases.go +++ b/apier/v1/aliases.go @@ -48,11 +48,18 @@ func (self *ApierV1) AddRatingSubjectAliases(attrs AttrAddRatingSubjectAliases, } var ignr string for _, alias := range attrs.Aliases { - if err := aliases.SetAlias( - engine.Alias{Direction: utils.META_OUT, Tenant: attrs.Tenant, Category: attrs.Category, Account: alias, Subject: alias, Context: utils.ALIAS_CONTEXT_RATING, - Values: engine.AliasValues{&engine.AliasValue{DestinationId: utils.META_ANY, - Pairs: engine.AliasPairs{"Subject": map[string]string{alias: attrs.Subject}}, Weight: 10.0}}}, &ignr); err != nil { - return utils.NewErrServerError(err) + als := engine.Alias{Direction: utils.META_OUT, Tenant: attrs.Tenant, Category: attrs.Category, Account: alias, Subject: alias, Context: utils.ALIAS_CONTEXT_RATING, + Values: engine.AliasValues{&engine.AliasValue{DestinationId: utils.META_ANY, + Pairs: engine.AliasPairs{"Subject": map[string]string{alias: attrs.Subject}}, Weight: 10.0}}} + var ignrAls engine.Alias + if err := aliases.GetAlias(als, &ignrAls); err == nil { // Update the previous alias if already there + if err := aliases.UpdateAlias(als, &ignr); err != nil { + return utils.NewErrServerError(err) + } + } else { + if err := aliases.SetAlias(als, &ignr); err != nil { + return utils.NewErrServerError(err) + } } } *reply = utils.OK @@ -100,11 +107,18 @@ func (self *ApierV1) AddAccountAliases(attrs AttrAddAccountAliases, reply *strin } var ignr string for _, alias := range attrs.Aliases { - if err := aliases.SetAlias( - engine.Alias{Direction: utils.META_OUT, Tenant: attrs.Tenant, Category: attrs.Category, Account: alias, Subject: alias, Context: utils.ALIAS_CONTEXT_RATING, - Values: engine.AliasValues{&engine.AliasValue{DestinationId: utils.META_ANY, - Pairs: engine.AliasPairs{"Account": map[string]string{alias: attrs.Account}}, Weight: 10.0}}}, &ignr); err != nil { - return utils.NewErrServerError(err) + als := engine.Alias{Direction: utils.META_OUT, Tenant: attrs.Tenant, Category: attrs.Category, Account: alias, Subject: alias, Context: utils.ALIAS_CONTEXT_RATING, + Values: engine.AliasValues{&engine.AliasValue{DestinationId: utils.META_ANY, + Pairs: engine.AliasPairs{"Account": map[string]string{alias: attrs.Account}}, Weight: 10.0}}} + var ignrAls engine.Alias + if err := aliases.GetAlias(als, &ignrAls); err == nil { // Update the previous alias if already there + if err := aliases.UpdateAlias(als, &ignr); err != nil { + return utils.NewErrServerError(err) + } + } else { + if err := aliases.SetAlias(als, &ignr); err != nil { + return utils.NewErrServerError(err) + } } } *reply = utils.OK diff --git a/apier/v2/cdrs_mysql_local_test.go b/apier/v2/cdrs_mysql_local_test.go index bcbb6c106..0cadbe1d4 100644 --- a/apier/v2/cdrs_mysql_local_test.go +++ b/apier/v2/cdrs_mysql_local_test.go @@ -23,7 +23,6 @@ import ( "net/rpc" "net/rpc/jsonrpc" "path" - "strconv" "testing" "time" @@ -305,6 +304,7 @@ func TestV2CdrsMysqlRateWithTP(t *testing.T) { } } +/* // Benchmark speed of processing 1000 CDRs func TestV2CdrsMysqlProcessRatedExternalCdrBenchmark(t *testing.T) { if !*testLocal { @@ -380,6 +380,7 @@ func TestV2CdrsMysqlProcessPostpaidExternalCdrBenchmark(t *testing.T) { t.Errorf("Processing of %d postpaid CDRs took: %v", nrCdrs, durExec) } } +*/ func TestV2CdrsMysqlKillEngine(t *testing.T) { if !*testLocal { diff --git a/apier/v2/cdrs_psql_local_test.go b/apier/v2/cdrs_psql_local_test.go index dc5d6e059..b02732245 100644 --- a/apier/v2/cdrs_psql_local_test.go +++ b/apier/v2/cdrs_psql_local_test.go @@ -23,7 +23,6 @@ import ( "net/rpc/jsonrpc" "os/exec" "path" - "strconv" "testing" "time" @@ -303,6 +302,7 @@ func TestV2CdrsPsqlRateWithTP(t *testing.T) { } } +/* // Benchmark speed of processing 1000 CDRs func TestV2CdrsPsqlProcessRatedExternalCdrBenchmark(t *testing.T) { if !*testLocal { @@ -378,6 +378,7 @@ func TestV2CdrsPsqlProcessPostpaidExternalCdrBenchmark(t *testing.T) { t.Errorf("Processing of %d postpaid CDRs took: %v", nrCdrs, durExec) } } +*/ func TestV2CdrsPsqlKillEngine(t *testing.T) { if !*testLocal { diff --git a/engine/aliases.go b/engine/aliases.go index eda219baa..fbcb61f93 100644 --- a/engine/aliases.go +++ b/engine/aliases.go @@ -195,14 +195,14 @@ func (am *AliasHandler) SetAlias(al Alias, reply *string) error { } func (am *AliasHandler) UpdateAlias(al Alias, reply *string) error { - am.mu.Lock() - defer am.mu.Unlock() // get previous value oldAlias := &Alias{} if err := am.GetAlias(al, oldAlias); err != nil { *reply = err.Error() return err } + am.mu.Lock() + defer am.mu.Unlock() for _, oldValue := range oldAlias.Values { found := false for _, value := range al.Values { diff --git a/engine/cdrs.go b/engine/cdrs.go index bb91737d9..34a0b7487 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -146,6 +146,22 @@ func (self *CdrServer) RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqT if cdr.MediationRunId == "" { // raw CDRs which were not calculated before cdr.MediationRunId = utils.META_DEFAULT } + // replace aliases for cases they were loaded after CDR received + if err := LoadAlias(&AttrMatchingAlias{ + Destination: cdr.Destination, + Direction: cdr.Direction, + Tenant: cdr.Tenant, + Category: cdr.Category, + Account: cdr.Account, + Subject: cdr.Subject, + Context: utils.ALIAS_CONTEXT_RATING, + }, cdr, utils.EXTRA_FIELDS); err != nil && err != utils.ErrNotFound { + return err + } + // replace user profile fields + if err := LoadUserProfile(cdr, utils.EXTRA_FIELDS); err != nil { + return err + } if err := self.rateStoreStatsReplicate(cdr); err != nil { utils.Logger.Err(fmt.Sprintf(" Processing CDR %+v, got error: %s", cdr, err.Error())) } From 27a8dd7d2ffb3d04e616f71f66c97565ebb9230f Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 27 Nov 2015 21:19:48 +0100 Subject: [PATCH 15/30] AddAccountAliases temporary fix since update not properly working --- apier/v1/aliases.go | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/apier/v1/aliases.go b/apier/v1/aliases.go index 531f97e14..d736cd270 100644 --- a/apier/v1/aliases.go +++ b/apier/v1/aliases.go @@ -50,16 +50,9 @@ func (self *ApierV1) AddRatingSubjectAliases(attrs AttrAddRatingSubjectAliases, for _, alias := range attrs.Aliases { als := engine.Alias{Direction: utils.META_OUT, Tenant: attrs.Tenant, Category: attrs.Category, Account: alias, Subject: alias, Context: utils.ALIAS_CONTEXT_RATING, Values: engine.AliasValues{&engine.AliasValue{DestinationId: utils.META_ANY, - Pairs: engine.AliasPairs{"Subject": map[string]string{alias: attrs.Subject}}, Weight: 10.0}}} - var ignrAls engine.Alias - if err := aliases.GetAlias(als, &ignrAls); err == nil { // Update the previous alias if already there - if err := aliases.UpdateAlias(als, &ignr); err != nil { - return utils.NewErrServerError(err) - } - } else { - if err := aliases.SetAlias(als, &ignr); err != nil { - return utils.NewErrServerError(err) - } + Pairs: engine.AliasPairs{"Account": map[string]string{alias: attrs.Subject}, "Subject": map[string]string{alias: attrs.Subject}}, Weight: 10.0}}} + if err := aliases.SetAlias(als, &ignr); err != nil { + return utils.NewErrServerError(err) } } *reply = utils.OK @@ -109,16 +102,9 @@ func (self *ApierV1) AddAccountAliases(attrs AttrAddAccountAliases, reply *strin for _, alias := range attrs.Aliases { als := engine.Alias{Direction: utils.META_OUT, Tenant: attrs.Tenant, Category: attrs.Category, Account: alias, Subject: alias, Context: utils.ALIAS_CONTEXT_RATING, Values: engine.AliasValues{&engine.AliasValue{DestinationId: utils.META_ANY, - Pairs: engine.AliasPairs{"Account": map[string]string{alias: attrs.Account}}, Weight: 10.0}}} - var ignrAls engine.Alias - if err := aliases.GetAlias(als, &ignrAls); err == nil { // Update the previous alias if already there - if err := aliases.UpdateAlias(als, &ignr); err != nil { - return utils.NewErrServerError(err) - } - } else { - if err := aliases.SetAlias(als, &ignr); err != nil { - return utils.NewErrServerError(err) - } + Pairs: engine.AliasPairs{"Account": map[string]string{alias: attrs.Account}, "Subject": map[string]string{alias: attrs.Account}}, Weight: 10.0}}} + if err := aliases.SetAlias(als, &ignr); err != nil { + return utils.NewErrServerError(err) } } *reply = utils.OK From 04e52d6e9778fa5db7a9d19d45da3c7d5719d866 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Sat, 28 Nov 2015 01:14:08 +0200 Subject: [PATCH 16/30] fix update aliases, further discuss #301 --- engine/aliases.go | 14 +++--- engine/aliases_test.go | 111 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/engine/aliases.go b/engine/aliases.go index fbcb61f93..5a8f68409 100644 --- a/engine/aliases.go +++ b/engine/aliases.go @@ -195,18 +195,18 @@ func (am *AliasHandler) SetAlias(al Alias, reply *string) error { } func (am *AliasHandler) UpdateAlias(al Alias, reply *string) error { - // get previous value - oldAlias := &Alias{} - if err := am.GetAlias(al, oldAlias); err != nil { - *reply = err.Error() - return err - } am.mu.Lock() defer am.mu.Unlock() + // get previous value + oldAlias, err := am.accountingDb.GetAlias(al.GetId(), false) + if err != nil { + return err + } + // move old values that were not overwritten into the new alias for _, oldValue := range oldAlias.Values { found := false for _, value := range al.Values { - if oldValue.Equals(value) { + if oldValue.DestinationId == value.DestinationId { found = true break } diff --git a/engine/aliases_test.go b/engine/aliases_test.go index 67d89dd9a..a0c5f2596 100644 --- a/engine/aliases_test.go +++ b/engine/aliases_test.go @@ -45,6 +45,117 @@ func TestAliasesGetMatchingAlias(t *testing.T) { } } +func TestAliasesSetters(t *testing.T) { + var out string + if err := aliasService.SetAlias(Alias{ + Direction: "*out", + Tenant: "cgrates.org", + Category: "call", + Account: "set", + Subject: "set", + Context: "*rating", + Values: AliasValues{&AliasValue{ + DestinationId: utils.ANY, + Pairs: AliasPairs{"Account": map[string]string{"1234": "1235"}}, + Weight: 10, + }}, + }, &out); err != nil || out != utils.OK { + t.Error("Error setting alias: ", err, out) + } + r := &Alias{} + if err := aliasService.GetAlias(Alias{ + Direction: "*out", + Tenant: "cgrates.org", + Category: "call", + Account: "set", + Subject: "set", + Context: "*rating", + }, r); err != nil || len(r.Values) != 1 || len(r.Values[0].Pairs) != 1 { + t.Errorf("Error getting alias: %+v", r) + } + + if err := aliasService.UpdateAlias(Alias{ + Direction: "*out", + Tenant: "cgrates.org", + Category: "call", + Account: "set", + Subject: "set", + Context: "*rating", + Values: AliasValues{&AliasValue{ + DestinationId: utils.ANY, + Pairs: AliasPairs{"Subject": map[string]string{"1234": "1235"}}, + Weight: 10, + }}, + }, &out); err != nil || out != utils.OK { + t.Error("Error updateing alias: ", err, out) + } + if err := aliasService.GetAlias(Alias{ + Direction: "*out", + Tenant: "cgrates.org", + Category: "call", + Account: "set", + Subject: "set", + Context: "*rating", + }, r); err != nil || len(r.Values) != 1 || len(r.Values[0].Pairs) != 1 || r.Values[0].Pairs["Subject"]["1234"] != "1235" { + t.Errorf("Error getting alias: %+v", r.Values[0]) + } + if err := aliasService.UpdateAlias(Alias{ + Direction: "*out", + Tenant: "cgrates.org", + Category: "call", + Account: "set", + Subject: "set", + Context: "*rating", + Values: AliasValues{&AliasValue{ + DestinationId: utils.ANY, + Pairs: AliasPairs{"Subject": map[string]string{"1111": "2222"}}, + Weight: 10, + }}, + }, &out); err != nil || out != utils.OK { + t.Error("Error updateing alias: ", err, out) + } + if err := aliasService.GetAlias(Alias{ + Direction: "*out", + Tenant: "cgrates.org", + Category: "call", + Account: "set", + Subject: "set", + Context: "*rating", + }, r); err != nil || len(r.Values) != 1 || len(r.Values[0].Pairs) != 1 || r.Values[0].Pairs["Subject"]["1111"] != "2222" { + t.Errorf("Error getting alias: %+v", r.Values[0].Pairs["Subject"]) + } + if err := aliasService.UpdateAlias(Alias{ + Direction: "*out", + Tenant: "cgrates.org", + Category: "call", + Account: "set", + Subject: "set", + Context: "*rating", + Values: AliasValues{&AliasValue{ + DestinationId: "NAT", + Pairs: AliasPairs{"Subject": map[string]string{"3333": "4444"}}, + Weight: 10, + }}, + }, &out); err != nil || out != utils.OK { + t.Error("Error updateing alias: ", err, out) + } + if err := aliasService.GetAlias(Alias{ + Direction: "*out", + Tenant: "cgrates.org", + Category: "call", + Account: "set", + Subject: "set", + Context: "*rating", + }, r); err != nil || + len(r.Values) != 2 || + len(r.Values[1].Pairs) != 1 || + r.Values[1].Pairs["Subject"]["1111"] != "2222" || + len(r.Values[0].Pairs) != 1 || + r.Values[0].Pairs["Subject"]["3333"] != "4444" { + t.Errorf("Error getting alias: %+v", r.Values) + } +} + func TestAliasesLoadAlias(t *testing.T) { var response string cd := &CallDescriptor{ From 54c73c3cde487f62de0bee451202ae9400e467c8 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Sun, 29 Nov 2015 09:47:43 +0200 Subject: [PATCH 17/30] update to fully merge aliases --- engine/aliases.go | 21 ++++++++++++++++----- engine/aliases_test.go | 17 +++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/engine/aliases.go b/engine/aliases.go index 5a8f68409..a9fb00c65 100644 --- a/engine/aliases.go +++ b/engine/aliases.go @@ -202,21 +202,32 @@ func (am *AliasHandler) UpdateAlias(al Alias, reply *string) error { if err != nil { return err } - // move old values that were not overwritten into the new alias - for _, oldValue := range oldAlias.Values { + for _, value := range al.Values { found := false - for _, value := range al.Values { + if value.DestinationId == "" { + value.DestinationId = utils.ANY + } + for _, oldValue := range oldAlias.Values { if oldValue.DestinationId == value.DestinationId { + for target, origAliasMap := range value.Pairs { + for orig, alias := range origAliasMap { + if oldValue.Pairs[target] == nil { + oldValue.Pairs[target] = make(map[string]string) + } + oldValue.Pairs[target][orig] = alias + } + } + oldValue.Weight = value.Weight found = true break } } if !found { - al.Values = append(al.Values, oldValue) + oldAlias.Values = append(oldAlias.Values, value) } } - if err := am.accountingDb.SetAlias(&al); err != nil { + if err := am.accountingDb.SetAlias(oldAlias); err != nil { *reply = err.Error() return err } //add to cache diff --git a/engine/aliases_test.go b/engine/aliases_test.go index a0c5f2596..a95e40b1e 100644 --- a/engine/aliases_test.go +++ b/engine/aliases_test.go @@ -96,7 +96,11 @@ func TestAliasesSetters(t *testing.T) { Account: "set", Subject: "set", Context: "*rating", - }, r); err != nil || len(r.Values) != 1 || len(r.Values[0].Pairs) != 1 || r.Values[0].Pairs["Subject"]["1234"] != "1235" { + }, r); err != nil || + len(r.Values) != 1 || + len(r.Values[0].Pairs) != 2 || + r.Values[0].Pairs["Subject"]["1234"] != "1235" || + r.Values[0].Pairs["Account"]["1234"] != "1235" { t.Errorf("Error getting alias: %+v", r.Values[0]) } if err := aliasService.UpdateAlias(Alias{ @@ -121,7 +125,7 @@ func TestAliasesSetters(t *testing.T) { Account: "set", Subject: "set", Context: "*rating", - }, r); err != nil || len(r.Values) != 1 || len(r.Values[0].Pairs) != 1 || r.Values[0].Pairs["Subject"]["1111"] != "2222" { + }, r); err != nil || len(r.Values) != 1 || len(r.Values[0].Pairs) != 2 || r.Values[0].Pairs["Subject"]["1111"] != "2222" { t.Errorf("Error getting alias: %+v", r.Values[0].Pairs["Subject"]) } if err := aliasService.UpdateAlias(Alias{ @@ -149,10 +153,11 @@ func TestAliasesSetters(t *testing.T) { }, r); err != nil || len(r.Values) != 2 || len(r.Values[1].Pairs) != 1 || - r.Values[1].Pairs["Subject"]["1111"] != "2222" || - len(r.Values[0].Pairs) != 1 || - r.Values[0].Pairs["Subject"]["3333"] != "4444" { - t.Errorf("Error getting alias: %+v", r.Values) + r.Values[1].Pairs["Subject"]["3333"] != "4444" || + len(r.Values[0].Pairs) != 2 || + r.Values[0].Pairs["Subject"]["1111"] != "2222" || + r.Values[0].Pairs["Subject"]["1234"] != "1235" { + t.Errorf("Error getting alias: %+v", r.Values[0]) } } From 514138f17575522c6c1d172c3a903360e0e154dc Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 29 Nov 2015 13:01:18 +0100 Subject: [PATCH 18/30] Diameter config debit_interval and dialect; implementation of diameter handler *ccr_usage; fix of usageForCCR in case of updates --- agents/dmtagent.go | 5 ++- agents/dmtagent_it_test.go | 60 +++++++++++++++++++++++-- agents/libdmt.go | 25 ++++++----- agents/libdmt_test.go | 9 +++- apier/v1/derivedcharging_test.go | 3 -- apier/v1/smgenericv1.go | 12 ++--- config/config_defaults.go | 4 +- config/config_json_test.go | 6 ++- config/daconfig.go | 10 ++++- config/libconfig_json.go | 2 + data/conf/samples/dmtagent/cgrates.json | 2 +- sessionmanager/smgeneric.go | 4 ++ 12 files changed, 111 insertions(+), 31 deletions(-) diff --git a/agents/dmtagent.go b/agents/dmtagent.go index d8e227da1..bde55c0eb 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -89,6 +89,9 @@ func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestPro var rpl string err = self.smg.Call("SMGenericV1.SessionEnd", smgEv, &rpl) } + if err != nil { + return nil, err + } cca := NewCCAFromCCR(ccr) cca.OriginHost = self.cgrCfg.DiameterAgentCfg().OriginHost cca.OriginRealm = self.cgrCfg.DiameterAgentCfg().OriginRealm @@ -98,7 +101,7 @@ func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestPro } func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { - ccr, err := NewCCRFromDiameterMessage(m) + ccr, err := NewCCRFromDiameterMessage(m, self.cgrCfg.DiameterAgentCfg().DebitInterval) if err != nil { utils.Logger.Err(fmt.Sprintf(" Unmarshaling message: %s, error: %s", m, err)) return diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 0e1816863..512bf2998 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -113,6 +113,7 @@ func TestDmtAgentCCRAsSMGenericEvent(t *testing.T) { SubscriptionIdType int `avp:"Subscription-Id-Type"` SubscriptionIdData string `avp:"Subscription-Id-Data"` }{SubscriptionIdType: 0, SubscriptionIdData: "4986517174963"}}, + debitInterval: time.Duration(300) * time.Second, } ccr.RequestedServiceUnit.CCTime = 300 ccr.ServiceInformation.INInformation.CallingPartyAddress = "4986517174963" @@ -142,7 +143,7 @@ func TestDmtAgentCCRAsSMGenericEvent(t *testing.T) { } } -func TestDmtAgentSendCCR(t *testing.T) { +func TestDmtAgentSendCCRInit(t *testing.T) { if !*testIntegration { return } @@ -155,10 +156,62 @@ func TestDmtAgentSendCCR(t *testing.T) { AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", 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: utils.DEFAULT_RUNID, - Usage: time.Duration(10) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", Rated: true, + Usage: time.Duration(0) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, - daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, time.Duration(300)*time.Second, true) + daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) + m, err := ccr.AsDiameterMessage() + if err != nil { + t.Error(err) + } + if err := dmtClient.SendMessage(m); err != nil { + t.Error(err) + } +} + +func TestDmtAgentSendCCRUpdate(t *testing.T) { + if !*testIntegration { + return + } + dmtClient, err := NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, + daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DictionariesDir) + if err != nil { + t.Fatal(err) + } + cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, + AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", + Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", + 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: utils.DEFAULT_RUNID, + Usage: time.Duration(610) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + } + ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, + daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) + m, err := ccr.AsDiameterMessage() + if err != nil { + t.Error(err) + } + if err := dmtClient.SendMessage(m); err != nil { + t.Error(err) + } +} + +func TestDmtAgentSendCCRTerminate(t *testing.T) { + if !*testIntegration { + return + } + dmtClient, err := NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, + daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DictionariesDir) + if err != nil { + t.Fatal(err) + } + cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, + AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", + Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", + 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: utils.DEFAULT_RUNID, + Usage: time.Duration(610) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + } + ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, + daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, true) m, err := ccr.AsDiameterMessage() if err != nil { t.Error(err) @@ -166,7 +219,6 @@ func TestDmtAgentSendCCR(t *testing.T) { if err := dmtClient.SendMessage(m); err != nil { t.Error(err) } - time.Sleep(time.Duration(10) * time.Second) } func TestDmtAgentStopEngine(t *testing.T) { diff --git a/agents/libdmt.go b/agents/libdmt.go index 5ac2c0951..610b7ab70 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -85,33 +85,35 @@ func loadDictionaries(dictsDir, componentId string) error { } // Returns reqType, requestNr and ccTime in seconds -func disectUsageForCCR(usage time.Duration, debitInterval time.Duration, callEnded bool) (int, int, int) { +func disectUsageForCCR(usage time.Duration, debitInterval time.Duration, callEnded bool) (reqType, reqNr, ccTime int) { usageSecs := usage.Seconds() debitIntervalSecs := debitInterval.Seconds() - reqType := 1 + reqType = 1 if usage > 0 { reqType = 2 } if callEnded { reqType = 3 } - reqNr := int(usageSecs / debitIntervalSecs) + reqNr = int(usageSecs / debitIntervalSecs) if callEnded { reqNr += 1 } - ccTime := debitInterval.Seconds() + ccTimeFloat := debitInterval.Seconds() if callEnded { - ccTime = math.Mod(usageSecs, debitIntervalSecs) + ccTimeFloat = math.Mod(usageSecs, debitIntervalSecs) } - return reqType, reqNr, int(ccTime) + return reqType, reqNr, int(ccTimeFloat) } func usageFromCCR(reqType, reqNr, ccTime int, debitIterval time.Duration) time.Duration { dISecs := debitIterval.Seconds() if reqType == 3 { reqNr -= 1 // decrease request number to reach the real number + ccTime += int(dISecs) * reqNr + } else { + ccTime = int(dISecs) } - ccTime += int(dISecs) * reqNr return time.Duration(ccTime) * time.Second } @@ -143,12 +145,14 @@ func storedCdrToCCR(cdr *engine.StoredCdr, originHost, originRealm string, vendo return ccr } -func NewCCRFromDiameterMessage(m *diam.Message) (*CCR, error) { +// debitInterval is the configured debitInterval, in sync with the diameter client one +func NewCCRFromDiameterMessage(m *diam.Message, debitInterval time.Duration) (*CCR, error) { var ccr CCR if err := m.Unmarshal(&ccr); err != nil { return nil, err } ccr.diamMessage = m + ccr.debitInterval = debitInterval return &ccr, nil } @@ -188,7 +192,8 @@ type CCR struct { SSPTime string `avp:"SSP-Time"` } `avp:"IN-Information"` } `avp:"Service-Information"` - diamMessage *diam.Message // Used to parse fields with CGR templates + diamMessage *diam.Message // Used to parse fields with CGR templates + debitInterval time.Duration // Configured debit interval } // Used when sending from client to agent @@ -353,7 +358,7 @@ func (self *CCR) passesFieldFilter(fieldFilter *utils.RSRField) bool { func (self *CCR) metaHandler(tag, arg string) (string, error) { switch tag { case META_CCR_USAGE: - usage := usageFromCCR(self.CCRequestType, self.CCRequestNumber, self.RequestedServiceUnit.CCTime, time.Duration(300)*time.Second) + usage := usageFromCCR(self.CCRequestType, self.CCRequestNumber, self.RequestedServiceUnit.CCTime, self.debitInterval) return strconv.FormatFloat(usage.Seconds(), 'f', -1, 64), nil } return "", nil diff --git a/agents/libdmt_test.go b/agents/libdmt_test.go index 78888d383..69010b523 100644 --- a/agents/libdmt_test.go +++ b/agents/libdmt_test.go @@ -40,10 +40,12 @@ func TestDisectUsageForCCR(t *testing.T) { if reqType, reqNr, ccTime := disectUsageForCCR(time.Duration(35)*time.Second, time.Duration(300)*time.Second, true); reqType != 3 || reqNr != 1 || ccTime != 35 { t.Error(reqType, reqNr, ccTime) } + if reqType, reqNr, ccTime := disectUsageForCCR(time.Duration(610)*time.Second, time.Duration(300)*time.Second, true); reqType != 3 || reqNr != 3 || ccTime != 10 { + t.Error(reqType, reqNr, ccTime) + } if reqType, reqNr, ccTime := disectUsageForCCR(time.Duration(935)*time.Second, time.Duration(300)*time.Second, true); reqType != 3 || reqNr != 4 || ccTime != 35 { t.Error(reqType, reqNr, ccTime) } - } func TestUsageFromCCR(t *testing.T) { @@ -53,7 +55,10 @@ func TestUsageFromCCR(t *testing.T) { if usage := usageFromCCR(2, 0, 300, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second { t.Error(usage) } - if usage := usageFromCCR(2, 3, 300, time.Duration(300)*time.Second); usage != time.Duration(1200)*time.Second { + if usage := usageFromCCR(2, 3, 300, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second { + t.Error(usage) + } + if usage := usageFromCCR(3, 3, 10, time.Duration(300)*time.Second); usage != time.Duration(610)*time.Second { t.Error(usage) } if usage := usageFromCCR(3, 4, 35, time.Duration(300)*time.Second); usage != time.Duration(935)*time.Second { diff --git a/apier/v1/derivedcharging_test.go b/apier/v1/derivedcharging_test.go index 2a1f542c3..dfc2df07c 100644 --- a/apier/v1/derivedcharging_test.go +++ b/apier/v1/derivedcharging_test.go @@ -97,9 +97,6 @@ func TestGetEmptyDC2(t *testing.T) { if err := apierDcT.GetDerivedChargers(attrs, &dcs); err != nil { t.Error("Unexpected error", err.Error()) } else if !reflect.DeepEqual(dcs, apierDcT.Config.DerivedChargers) { - for _, dc := range dcs { - fmt.Printf("Got dc: %v\n", dc) - } t.Error("Returned DerivedChargers not matching the configured ones") } } diff --git a/apier/v1/smgenericv1.go b/apier/v1/smgenericv1.go index 4ccfb2afb..2c07293e0 100644 --- a/apier/v1/smgenericv1.go +++ b/apier/v1/smgenericv1.go @@ -87,7 +87,7 @@ func (self *SMGenericV1) Call(serviceMethod string, args interface{}, reply inte if !canConvert { return rpcclient.ErrWrongArgsType } - replyConverted, canConvert := args.(*float64) + replyConverted, canConvert := reply.(*float64) if !canConvert { return rpcclient.ErrWrongReplyType } @@ -97,7 +97,7 @@ func (self *SMGenericV1) Call(serviceMethod string, args interface{}, reply inte if !canConvert { return rpcclient.ErrWrongArgsType } - replyConverted, canConvert := args.(*[]string) + replyConverted, canConvert := reply.(*[]string) if !canConvert { return rpcclient.ErrWrongReplyType } @@ -107,7 +107,7 @@ func (self *SMGenericV1) Call(serviceMethod string, args interface{}, reply inte if !canConvert { return rpcclient.ErrWrongArgsType } - replyConverted, canConvert := args.(*float64) + replyConverted, canConvert := reply.(*float64) if !canConvert { return rpcclient.ErrWrongReplyType } @@ -117,7 +117,7 @@ func (self *SMGenericV1) Call(serviceMethod string, args interface{}, reply inte if !canConvert { return rpcclient.ErrWrongArgsType } - replyConverted, canConvert := args.(*float64) + replyConverted, canConvert := reply.(*float64) if !canConvert { return rpcclient.ErrWrongReplyType } @@ -127,7 +127,7 @@ func (self *SMGenericV1) Call(serviceMethod string, args interface{}, reply inte if !canConvert { return rpcclient.ErrWrongArgsType } - replyConverted, canConvert := args.(*string) + replyConverted, canConvert := reply.(*string) if !canConvert { return rpcclient.ErrWrongReplyType } @@ -137,7 +137,7 @@ func (self *SMGenericV1) Call(serviceMethod string, args interface{}, reply inte if !canConvert { return rpcclient.ErrWrongArgsType } - replyConverted, canConvert := args.(*string) + replyConverted, canConvert := reply.(*string) if !canConvert { return rpcclient.ErrWrongReplyType } diff --git a/config/config_defaults.go b/config/config_defaults.go index 53753034e..49a09c559 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -264,7 +264,9 @@ const CGRATES_CFG_JSON = ` "listen": "127.0.0.1:3868", // address where to listen for diameter requests "dictionaries_dir": "/usr/share/cgrates/diameter/dict/", // path towards directory holding additional dictionaries to load "sm_generic": "internal", // connection towards SMG component for session management + "debit_interval": "5m", // interval for CCR updates "timezone": "", // timezone for timestamps where not specified, empty for general defaults <""|UTC|Local|$IANA_TZ_DB> + "dialect": "huawei", // the diameter dialect used in the communication, supported: "origin_host": "CGR-DA", // diameter Origin-Host AVP used in replies "origin_realm": "cgrates.org", // diameter Origin-Realm AVP used in replies "vendor_id": 0, // diameter Vendor-Id AVP used in replies @@ -287,7 +289,7 @@ const CGRATES_CFG_JSON = ` {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, {"tag": "setup_time", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, {"tag": "answer_time", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "usage", "field_id": "Usage", "type": "*composed", "value": "Requested-Service-Unit>CC-Time", "mandatory": true}, + {"tag": "usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, {"tag": "subscriber_id", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, ], }, diff --git a/config/config_json_test.go b/config/config_json_test.go index 2ab9edcfa..50a51d0c1 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -420,7 +420,9 @@ func TestDiameterAgentJsonCfg(t *testing.T) { Listen: utils.StringPointer("127.0.0.1:3868"), Dictionaries_dir: utils.StringPointer("/usr/share/cgrates/diameter/dict/"), Sm_generic: utils.StringPointer("internal"), + Debit_interval: utils.StringPointer("5m"), Timezone: utils.StringPointer(""), + Dialect: utils.StringPointer("huawei"), Origin_host: utils.StringPointer("CGR-DA"), Origin_realm: utils.StringPointer("cgrates.org"), Vendor_id: utils.IntPointer(0), @@ -454,8 +456,8 @@ func TestDiameterAgentJsonCfg(t *testing.T) { Value: utils.StringPointer("Event-Timestamp"), Mandatory: utils.BoolPointer(true)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("answer_time"), Field_id: utils.StringPointer(utils.ANSWER_TIME), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Event-Timestamp"), Mandatory: utils.BoolPointer(true)}, - &CdrFieldJsonCfg{Tag: utils.StringPointer("usage"), Field_id: utils.StringPointer(utils.USAGE), Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("Requested-Service-Unit>CC-Time"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("usage"), Field_id: utils.StringPointer(utils.USAGE), Type: utils.StringPointer(utils.META_HANDLER), + Handler_id: utils.StringPointer("*ccr_usage"), Mandatory: utils.BoolPointer(true)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("subscriber_id"), Field_id: utils.StringPointer("SubscriberId"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Subscription-Id>Subscription-Id-Data"), Mandatory: utils.BoolPointer(true)}, }, diff --git a/config/daconfig.go b/config/daconfig.go index c714d1e00..c358dfa8a 100644 --- a/config/daconfig.go +++ b/config/daconfig.go @@ -19,7 +19,7 @@ along with this program. If not, see package config import ( - //"time" + "time" "github.com/cgrates/cgrates/utils" ) @@ -29,7 +29,9 @@ type DiameterAgentCfg struct { Listen string // address where to listen for diameter requests DictionariesDir string SMGeneric string // connection towards SMG component + DebitInterval time.Duration Timezone string // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> + Dialect string // the diameter dialect used in the implementation OriginHost string OriginRealm string VendorId int @@ -53,6 +55,12 @@ func (self *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg) erro if jsnCfg.Sm_generic != nil { self.SMGeneric = *jsnCfg.Sm_generic } + if jsnCfg.Debit_interval != nil { + var err error + if self.DebitInterval, err = utils.ParseDurationWithSecs(*jsnCfg.Debit_interval); err != nil { + return err + } + } if jsnCfg.Timezone != nil { self.Timezone = *jsnCfg.Timezone } diff --git a/config/libconfig_json.go b/config/libconfig_json.go index fa013c5d1..82240478d 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -241,7 +241,9 @@ type DiameterAgentJsonCfg struct { Listen *string // address where to listen for diameter requests Dictionaries_dir *string // path towards additional dictionaries Sm_generic *string // Connection towards generic SM + Debit_interval *string Timezone *string // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> + Dialect *string Origin_host *string Origin_realm *string Vendor_id *int diff --git a/data/conf/samples/dmtagent/cgrates.json b/data/conf/samples/dmtagent/cgrates.json index 210a91b55..1a8c3a1f0 100644 --- a/data/conf/samples/dmtagent/cgrates.json +++ b/data/conf/samples/dmtagent/cgrates.json @@ -73,7 +73,7 @@ {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, {"tag": "setup_time", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, {"tag": "answer_time", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "usage", "field_id": "Usage", "type": "*composed", "value": "Requested-Service-Unit>CC-Time", "mandatory": true}, + {"tag": "usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, {"tag": "subscriber_id", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, ], }, diff --git a/sessionmanager/smgeneric.go b/sessionmanager/smgeneric.go index 9d0490fca..58048b8fd 100644 --- a/sessionmanager/smgeneric.go +++ b/sessionmanager/smgeneric.go @@ -160,6 +160,7 @@ func (self *SMGeneric) GetLcrSuppliers(gev SMGenericEvent, clnt *rpc2.Client) ([ // Execute debits for usage/maxUsage func (self *SMGeneric) SessionUpdate(gev SMGenericEvent, clnt *rpc2.Client) (time.Duration, error) { + utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionUpdate, event: %+v", gev)) var minMaxUsage time.Duration evMaxUsage, err := gev.GetMaxUsage(utils.META_DEFAULT, self.cgrCfg.MaxCallDuration) if err != nil { @@ -175,11 +176,13 @@ func (self *SMGeneric) SessionUpdate(gev SMGenericEvent, clnt *rpc2.Client) (tim } } } + utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionUpdate, return minMaxUsage: %+v", minMaxUsage)) return minMaxUsage, nil } // Called on session start func (self *SMGeneric) SessionStart(gev SMGenericEvent, clnt *rpc2.Client) (time.Duration, error) { + utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionStart, event: %+v", gev)) if err := self.sessionStart(gev, getClientConnId(clnt)); err != nil { return nilDuration, err } @@ -188,6 +191,7 @@ func (self *SMGeneric) SessionStart(gev SMGenericEvent, clnt *rpc2.Client) (time // Called on session end, should stop debit loop func (self *SMGeneric) SessionEnd(gev SMGenericEvent, clnt *rpc2.Client) error { + utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionEnd, event: %+v", gev)) endTime, err := gev.GetEndTime(utils.META_DEFAULT, self.timezone) if err != nil { return err From 00b6f0f9539f6c29432e7b2836fe6c0c6d31d0e6 Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 29 Nov 2015 16:04:26 +0100 Subject: [PATCH 19/30] SessionManger fix getClietnConnId for empty connection, removed MAX_USAGE in favor of USAGE fields --- agents/dmtagent_it_test.go | 30 +++++++++++++++++++- config/config_defaults.go | 2 +- config/config_json_test.go | 2 +- data/conf/samples/dmtagent/cgrates.json | 37 ++++--------------------- data/tariffplans/tutorial/Users.csv | 3 ++ sessionmanager/smg_event.go | 2 +- sessionmanager/smg_externalconns.go | 3 ++ sessionmanager/smgeneric.go | 4 +-- utils/consts.go | 1 - 9 files changed, 45 insertions(+), 39 deletions(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 512bf2998..7eed1c19f 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -20,6 +20,8 @@ package agents import ( "flag" + "net/rpc" + "net/rpc/jsonrpc" "path" "reflect" "testing" @@ -37,6 +39,7 @@ var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path h var daCfgPath string var daCfg *config.CGRConfig +var apierRpc *rpc.Client func TestDmtAgentInitCfg(t *testing.T) { if !*testIntegration { @@ -133,7 +136,7 @@ func TestDmtAgentCCRAsSMGenericEvent(t *testing.T) { t.Error(err) } eSMGE := sessionmanager.SMGenericEvent{"EventName": "DIAMETER_CCR", "AccId": "routinga;1442095190;1476802709", - "Account": "*users", "AnswerTime": "2015-11-23 12:22:24 +0000 UTC", "Category": "call_4912395676749123956767", + "Account": "*users", "AnswerTime": "2015-11-23 12:22:24 +0000 UTC", "Category": "call", "Destination": "4986517174964", "Direction": "*out", "ReqType": "*users", "SetupTime": "2015-11-23 12:22:24 +0000 UTC", "Subject": "*users", "SubscriberId": "4986517174963", "TOR": "*voice", "Tenant": "*users", "Usage": "300"} if smge, err := ccr.AsSMGenericEvent(cfgDefaults.DiameterAgentCfg().RequestProcessors[0].ContentFields); err != nil { @@ -143,6 +146,31 @@ func TestDmtAgentCCRAsSMGenericEvent(t *testing.T) { } } +// Connect rpc client to rater +func TestDmtAgentApierRpcConn(t *testing.T) { + if !*testIntegration { + return + } + var err error + apierRpc, err = jsonrpc.Dial("tcp", daCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal(err) + } +} + +// Load the tariff plan, creating accounts and their balances +func TestDmtAgentTPFromFolder(t *testing.T) { + if !*testIntegration { + return + } + attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "tutorial")} + var loadInst engine.LoadInstance + if err := apierRpc.Call("ApierV2.LoadTariffPlanFromFolder", attrs, &loadInst); err != nil { + t.Error(err) + } + time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups +} + func TestDmtAgentSendCCRInit(t *testing.T) { if !*testIntegration { return diff --git a/config/config_defaults.go b/config/config_defaults.go index 49a09c559..0ffd77b09 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -283,7 +283,7 @@ const CGRATES_CFG_JSON = ` {"tag": "reqtype", "field_id": "ReqType", "type": "*composed", "value": "^*users", "mandatory": true}, {"tag": "direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, {"tag": "tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "category", "field_id": "Category", "type": "*composed", "value": "^call_;~Service-Information>IN-Information>Calling-Vlr-Number:s/^$/33000/;~Service-Information>IN-Information>Calling-Vlr-Number:s/^(\\d{5})/${1}/", "mandatory": true}, + {"tag": "category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, {"tag": "account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, {"tag": "subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, diff --git a/config/config_json_test.go b/config/config_json_test.go index 50a51d0c1..b618bb1c8 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -445,7 +445,7 @@ func TestDiameterAgentJsonCfg(t *testing.T) { &CdrFieldJsonCfg{Tag: utils.StringPointer("tenant"), Field_id: utils.StringPointer(utils.TENANT), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("category"), Field_id: utils.StringPointer(utils.CATEGORY), Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("^call_;~Service-Information>IN-Information>Calling-Vlr-Number:s/^$/33000/;~Service-Information>IN-Information>Calling-Vlr-Number:s/^(\\d{5})/${1}/"), Mandatory: utils.BoolPointer(true)}, + Value: utils.StringPointer("^call"), Mandatory: utils.BoolPointer(true)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("account"), Field_id: utils.StringPointer(utils.ACCOUNT), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("subject"), Field_id: utils.StringPointer(utils.SUBJECT), Type: utils.StringPointer(utils.META_COMPOSED), diff --git a/data/conf/samples/dmtagent/cgrates.json b/data/conf/samples/dmtagent/cgrates.json index 1a8c3a1f0..ff1387e85 100644 --- a/data/conf/samples/dmtagent/cgrates.json +++ b/data/conf/samples/dmtagent/cgrates.json @@ -41,43 +41,18 @@ }, "users": { - "enabled": true, // starts User service: . - "indexes": ["Uuid"], // user profile field indexes + "enabled": true, + "indexes": ["SubscriberId"], }, "sm_generic": { - "enabled": true, // starts SessionManager service: - "rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013> - "cdrs": "internal", // address where to reach CDR Server <""|internal|x.y.z.y:1234> + "enabled": true, + "rater": "internal", + "cdrs": "internal", }, "diameter_agent": { - "enabled": true, // enables the diameter agent: - "listen": "127.0.0.1:3868", // address where to listen for diameter requests - //"dictionaries_dir": "", // path towards directory holding additional dictionaries to load - "request_processors": [ - { - "id": "*default", // formal identifier of this processor - "dry_run": false, // do not send the CDRs to CDRS, just parse them - "request_filter": "Subscription-Id>Subscription-Id-Type(0)", // filter requests processed by this processor - "continue_on_success": false, // continue to the next template if executed - "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value - {"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "^*voice", "mandatory": true}, - {"tag": "accid", "field_id": "AccId", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "reqtype", "field_id": "ReqType", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "category", "field_id": "Category", "type": "*composed", "value": "^call_;~Service-Information>IN-Information>Calling-Vlr-Number:s/^$/33000/;~Service-Information>IN-Information>Calling-Vlr-Number:s/^(\\d{5})/${1}/", "mandatory": true}, - {"tag": "account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, - {"tag": "setup_time", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "answer_time", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, - {"tag": "subscriber_id", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, - ], - }, - ], + "enabled": true, }, } diff --git a/data/tariffplans/tutorial/Users.csv b/data/tariffplans/tutorial/Users.csv index be6f1614a..699dec1e2 100644 --- a/data/tariffplans/tutorial/Users.csv +++ b/data/tariffplans/tutorial/Users.csv @@ -5,15 +5,18 @@ cgrates.org,1001,,Cli,+4986517174963,10 cgrates.org,1001,,Account,1001,10 cgrates.org,1001,,Subject,1001,10 cgrates.org,1001,,Uuid,388539dfd4f5cefee8f488b78c6c244b9e19138e,10 +cgrates.org,1001,,SubscriberId,1001,10 cgrates.org,1001,,ReqType,*prepaid,10 cgrates.org,1002,,SysUserName,rif,10 cgrates.org,1002,,RifAttr,RifVal,10 cgrates.org,1002,,Account,1002,10 cgrates.org,1002,,Subject,1002,10 cgrates.org,1002,,Uuid,27f37edec0670fa34cf79076b80ef5021e39c5b5,10 +cgrates.org,1002,,SubscriberId,1002,10 cgrates.org,1004,,SysUserName,danb4,10 cgrates.org,1004,,SysPassword,hisPass321,10 cgrates.org,1004,,Cli,+4986517174964,10 cgrates.org,1004,,Account,1004,10 cgrates.org,1004,,Subject,1004,10 cgrates.org,1004,,ReqType,*rated,10 +cgrates.org,1004,,SubscriberId,1004,10 diff --git a/sessionmanager/smg_event.go b/sessionmanager/smg_event.go index e32553522..070d4c528 100644 --- a/sessionmanager/smg_event.go +++ b/sessionmanager/smg_event.go @@ -161,7 +161,7 @@ func (self SMGenericEvent) GetUsage(fieldName string) (time.Duration, error) { func (self SMGenericEvent) GetMaxUsage(fieldName string, cfgMaxUsage time.Duration) (time.Duration, error) { if fieldName == utils.META_DEFAULT { - fieldName = utils.MAX_USAGE + fieldName = utils.USAGE } maxUsageStr, hasIt := self[fieldName] if !hasIt { diff --git a/sessionmanager/smg_externalconns.go b/sessionmanager/smg_externalconns.go index 9057fc8f7..7838915ed 100644 --- a/sessionmanager/smg_externalconns.go +++ b/sessionmanager/smg_externalconns.go @@ -32,6 +32,9 @@ var ErrConnectionNotFound = errors.New("CONNECTION_NOT_FOUND") // Attempts to get the connId previously set in the client state container func getClientConnId(clnt *rpc2.Client) string { + if clnt == nil { + return "" + } uuid, hasIt := clnt.State.Get(CGR_CONNUUID) if !hasIt { return "" diff --git a/sessionmanager/smgeneric.go b/sessionmanager/smgeneric.go index 58048b8fd..3068cdda7 100644 --- a/sessionmanager/smgeneric.go +++ b/sessionmanager/smgeneric.go @@ -160,7 +160,6 @@ func (self *SMGeneric) GetLcrSuppliers(gev SMGenericEvent, clnt *rpc2.Client) ([ // Execute debits for usage/maxUsage func (self *SMGeneric) SessionUpdate(gev SMGenericEvent, clnt *rpc2.Client) (time.Duration, error) { - utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionUpdate, event: %+v", gev)) var minMaxUsage time.Duration evMaxUsage, err := gev.GetMaxUsage(utils.META_DEFAULT, self.cgrCfg.MaxCallDuration) if err != nil { @@ -176,13 +175,12 @@ func (self *SMGeneric) SessionUpdate(gev SMGenericEvent, clnt *rpc2.Client) (tim } } } - utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionUpdate, return minMaxUsage: %+v", minMaxUsage)) + utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionUpdate event: %+v, return minMaxUsage: %+v", gev, minMaxUsage)) return minMaxUsage, nil } // Called on session start func (self *SMGeneric) SessionStart(gev SMGenericEvent, clnt *rpc2.Client) (time.Duration, error) { - utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionStart, event: %+v", gev)) if err := self.sessionStart(gev, getClientConnId(clnt)); err != nil { return nilDuration, err } diff --git a/utils/consts.go b/utils/consts.go index 89d0febe1..66c21ecad 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -130,7 +130,6 @@ const ( COST_DETAILS = "CostDetails" RATED = "rated" RATED_FLD = "Rated" - MAX_USAGE = "MaxUsage" DEFAULT_RUNID = "*default" META_DEFAULT = "*default" STATIC_VALUE_PREFIX = "^" From ea5a8a38936c1ba07f7c24aa03ccd39232bd8986 Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 29 Nov 2015 17:09:20 +0100 Subject: [PATCH 20/30] CDRS fix answer time in case of unanswered calls, fixes #300 --- engine/cdrs.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/engine/cdrs.go b/engine/cdrs.go index 34a0b7487..787c9ccb7 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -207,6 +207,7 @@ func (self *CdrServer) processCdr(storedCdr *StoredCdr) (err error) { utils.Logger.Err(fmt.Sprintf(" Storing primary CDR %+v, got error: %s", storedCdr, err.Error())) return err // Error is propagated back and we don't continue processing the CDR if we cannot store it } + } go self.deriveRateStoreStatsReplicate(storedCdr) return nil @@ -341,6 +342,10 @@ func (self *CdrServer) deriveCdrs(storedCdr *StoredCdr) ([]*StoredCdr, error) { func (self *CdrServer) getCostFromRater(storedCdr *StoredCdr) (*CallCost, error) { cc := new(CallCost) var err error + timeStart := storedCdr.AnswerTime + if timeStart.IsZero() { // Fix for FreeSWITCH unanswered calls + timeStart = storedCdr.SetupTime + } cd := &CallDescriptor{ TOR: storedCdr.TOR, Direction: storedCdr.Direction, @@ -349,8 +354,8 @@ func (self *CdrServer) getCostFromRater(storedCdr *StoredCdr) (*CallCost, error) Subject: storedCdr.Subject, Account: storedCdr.Account, Destination: storedCdr.Destination, - TimeStart: storedCdr.AnswerTime, - TimeEnd: storedCdr.AnswerTime.Add(storedCdr.Usage), + TimeStart: timeStart, + TimeEnd: timeStart.Add(storedCdr.Usage), DurationIndex: storedCdr.Usage, } if utils.IsSliceMember([]string{utils.META_PSEUDOPREPAID, utils.META_POSTPAID, utils.META_PREPAID, utils.PSEUDOPREPAID, utils.POSTPAID, utils.PREPAID}, storedCdr.ReqType) { // Prepaid - Cost can be recalculated in case of missing records from SM From 90e67d69654b75c9f0bdc08575439c16b5b06e1d Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 29 Nov 2015 18:29:38 +0100 Subject: [PATCH 21/30] Fix maxUsage returned in SMGeneric.SessionUpdate, all start/update/endSession operational in SMGeneric and Diameter --- agents/dmtagent_it_test.go | 31 ++++++++++++++----------------- agents/libdmt.go | 4 ++-- sessionmanager/smgeneric.go | 9 +++------ 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 7eed1c19f..c9e5fb8e0 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -20,6 +20,7 @@ package agents import ( "flag" + "fmt" "net/rpc" "net/rpc/jsonrpc" "path" @@ -40,6 +41,8 @@ var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path h var daCfgPath string var daCfg *config.CGRConfig var apierRpc *rpc.Client +var dmtClient *DiameterClient +var err error func TestDmtAgentInitCfg(t *testing.T) { if !*testIntegration { @@ -175,15 +178,15 @@ func TestDmtAgentSendCCRInit(t *testing.T) { if !*testIntegration { return } - dmtClient, err := NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, + dmtClient, err = NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DictionariesDir) if err != nil { t.Fatal(err) } - cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, + cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", - 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: utils.DEFAULT_RUNID, + SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, Usage: time.Duration(0) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, @@ -195,21 +198,17 @@ func TestDmtAgentSendCCRInit(t *testing.T) { if err := dmtClient.SendMessage(m); err != nil { t.Error(err) } + time.Sleep(time.Duration(100) * time.Millisecond) } func TestDmtAgentSendCCRUpdate(t *testing.T) { if !*testIntegration { return } - dmtClient, err := NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, - daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DictionariesDir) - if err != nil { - t.Fatal(err) - } - cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, + cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", - 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: utils.DEFAULT_RUNID, + SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, Usage: time.Duration(610) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, @@ -218,24 +217,21 @@ func TestDmtAgentSendCCRUpdate(t *testing.T) { if err != nil { t.Error(err) } + fmt.Printf("Will send out message: %+v\n", ccr) if err := dmtClient.SendMessage(m); err != nil { t.Error(err) } + time.Sleep(time.Duration(100) * time.Millisecond) } func TestDmtAgentSendCCRTerminate(t *testing.T) { if !*testIntegration { return } - dmtClient, err := NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, - daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DictionariesDir) - if err != nil { - t.Fatal(err) - } - cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, + cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", - 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: utils.DEFAULT_RUNID, + SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, Usage: time.Duration(610) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, @@ -247,6 +243,7 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) { if err := dmtClient.SendMessage(m); err != nil { t.Error(err) } + time.Sleep(time.Duration(100) * time.Millisecond) } func TestDmtAgentStopEngine(t *testing.T) { diff --git a/agents/libdmt.go b/agents/libdmt.go index 610b7ab70..f9492dba6 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -120,9 +120,9 @@ func usageFromCCR(reqType, reqNr, ccTime int, debitIterval time.Duration) time.D // Utility function to convert from StoredCdr to CCR struct func storedCdrToCCR(cdr *engine.StoredCdr, originHost, originRealm string, vendorId int, productName string, firmwareRev int, debitInterval time.Duration, callEnded bool) *CCR { - sid := "session;" + strconv.Itoa(int(rand.Uint32())) + //sid := "session;" + strconv.Itoa(int(rand.Uint32())) reqType, reqNr, ccTime := disectUsageForCCR(cdr.Usage, debitInterval, callEnded) - ccr := &CCR{SessionId: sid, OriginHost: originHost, OriginRealm: originRealm, DestinationHost: originHost, DestinationRealm: originRealm, + ccr := &CCR{SessionId: cdr.CgrId, OriginHost: originHost, OriginRealm: originRealm, DestinationHost: originHost, DestinationRealm: originRealm, AuthApplicationId: 4, ServiceContextId: cdr.ExtraFields["Service-Context-Id"], CCRequestType: reqType, CCRequestNumber: reqNr, EventTimestamp: cdr.AnswerTime, ServiceIdentifier: 0} ccr.SubscriptionId = make([]struct { diff --git a/sessionmanager/smgeneric.go b/sessionmanager/smgeneric.go index 3068cdda7..275107608 100644 --- a/sessionmanager/smgeneric.go +++ b/sessionmanager/smgeneric.go @@ -160,7 +160,6 @@ func (self *SMGeneric) GetLcrSuppliers(gev SMGenericEvent, clnt *rpc2.Client) ([ // Execute debits for usage/maxUsage func (self *SMGeneric) SessionUpdate(gev SMGenericEvent, clnt *rpc2.Client) (time.Duration, error) { - var minMaxUsage time.Duration evMaxUsage, err := gev.GetMaxUsage(utils.META_DEFAULT, self.cgrCfg.MaxCallDuration) if err != nil { return nilDuration, err @@ -170,13 +169,12 @@ func (self *SMGeneric) SessionUpdate(gev SMGenericEvent, clnt *rpc2.Client) (tim if maxDur, err := s.debit(evMaxUsage); err != nil { return nilDuration, err } else { - if maxDur < minMaxUsage { - minMaxUsage = maxDur + if maxDur < evMaxUsage { + evMaxUsage = maxDur } } } - utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionUpdate event: %+v, return minMaxUsage: %+v", gev, minMaxUsage)) - return minMaxUsage, nil + return evMaxUsage, nil } // Called on session start @@ -189,7 +187,6 @@ func (self *SMGeneric) SessionStart(gev SMGenericEvent, clnt *rpc2.Client) (time // Called on session end, should stop debit loop func (self *SMGeneric) SessionEnd(gev SMGenericEvent, clnt *rpc2.Client) error { - utils.Logger.Debug(fmt.Sprintf("SMGEneric.SessionEnd, event: %+v", gev)) endTime, err := gev.GetEndTime(utils.META_DEFAULT, self.timezone) if err != nil { return err From 251129f5431fc99ff4c2d7bffbdd7732b15e1b01 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 30 Nov 2015 09:27:55 +0100 Subject: [PATCH 22/30] DiameterAgent ProcessCdr implementation --- agents/dmtagent.go | 3 +++ apier/v1/smgenericv1.go | 2 +- sessionmanager/smgeneric.go | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/agents/dmtagent.go b/agents/dmtagent.go index bde55c0eb..f8c6e4a67 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -88,6 +88,9 @@ func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestPro case 3: var rpl string err = self.smg.Call("SMGenericV1.SessionEnd", smgEv, &rpl) + if errCdr := self.smg.Call("SMGenericV1.ProcessCdr", smgEv, &rpl); errCdr != nil { + err = errCdr + } } if err != nil { return nil, err diff --git a/apier/v1/smgenericv1.go b/apier/v1/smgenericv1.go index 2c07293e0..864ba1c5f 100644 --- a/apier/v1/smgenericv1.go +++ b/apier/v1/smgenericv1.go @@ -141,7 +141,7 @@ func (self *SMGenericV1) Call(serviceMethod string, args interface{}, reply inte if !canConvert { return rpcclient.ErrWrongReplyType } - return self.SessionEnd(argsConverted, replyConverted) + return self.ProcessCdr(argsConverted, replyConverted) } return rpcclient.ErrUnsupporteServiceMethod } diff --git a/sessionmanager/smgeneric.go b/sessionmanager/smgeneric.go index 275107608..f6f956ab6 100644 --- a/sessionmanager/smgeneric.go +++ b/sessionmanager/smgeneric.go @@ -199,6 +199,7 @@ func (self *SMGeneric) SessionEnd(gev SMGenericEvent, clnt *rpc2.Client) error { func (self *SMGeneric) ProcessCdr(gev SMGenericEvent) error { var reply string + utils.Logger.Debug(fmt.Sprintf("SMGeneric.ProcessCdr: %+v", gev)) if err := self.cdrsrv.ProcessCdr(gev.AsStoredCdr(self.cgrCfg, self.timezone), &reply); err != nil { return err } From 6318c45ebb9d50d64f660e46118169719fa0c979 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 30 Nov 2015 11:42:29 +0100 Subject: [PATCH 23/30] Fix StoredCdr empty fieldName to point out towards default value --- engine/responder.go | 3 ++- engine/storedcdr.go | 30 +++++++++++++++--------------- sessionmanager/smgeneric.go | 1 - 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/engine/responder.go b/engine/responder.go index 531df85da..1b0cb9d85 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -341,7 +341,8 @@ func (rs *Responder) GetDerivedMaxSessionTime(ev *StoredCdr, reply *float64) err *reply = 0 return err } - if utils.IsSliceMember([]string{utils.META_POSTPAID, utils.POSTPAID}, ev.GetReqType(dc.ReqTypeField)) { // Only consider prepaid and pseudoprepaid for MaxSessionTime + if utils.IsSliceMember([]string{utils.META_POSTPAID, utils.POSTPAID}, ev.GetReqType(dc.ReqTypeField)) { + // Only consider prepaid and pseudoprepaid for MaxSessionTime, do it here for unauthorized destination error check continue } // Set maxCallDuration, smallest out of all forked sessions diff --git a/engine/storedcdr.go b/engine/storedcdr.go index c629abe2a..8c45f4c71 100644 --- a/engine/storedcdr.go +++ b/engine/storedcdr.go @@ -557,7 +557,7 @@ func (storedCdr *StoredCdr) GetSessionIds() []string { return []string{storedCdr.GetUUID()} } func (storedCdr *StoredCdr) GetDirection(fieldName string) string { - if utils.IsSliceMember([]string{utils.DIRECTION, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.DIRECTION, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Direction } if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value @@ -566,7 +566,7 @@ func (storedCdr *StoredCdr) GetDirection(fieldName string) string { return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetSubject(fieldName string) string { - if utils.IsSliceMember([]string{utils.SUBJECT, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.SUBJECT, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Subject } if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value @@ -575,7 +575,7 @@ func (storedCdr *StoredCdr) GetSubject(fieldName string) string { return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetAccount(fieldName string) string { - if utils.IsSliceMember([]string{utils.ACCOUNT, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.ACCOUNT, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Account } if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value @@ -584,7 +584,7 @@ func (storedCdr *StoredCdr) GetAccount(fieldName string) string { return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetDestination(fieldName string) string { - if utils.IsSliceMember([]string{utils.DESTINATION, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.DESTINATION, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Destination } if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value @@ -593,7 +593,7 @@ func (storedCdr *StoredCdr) GetDestination(fieldName string) string { return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetCallDestNr(fieldName string) string { - if utils.IsSliceMember([]string{utils.DESTINATION, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.DESTINATION, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Destination } if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value @@ -602,7 +602,7 @@ func (storedCdr *StoredCdr) GetCallDestNr(fieldName string) string { return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetCategory(fieldName string) string { - if utils.IsSliceMember([]string{utils.CATEGORY, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.CATEGORY, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Category } if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value @@ -611,7 +611,7 @@ func (storedCdr *StoredCdr) GetCategory(fieldName string) string { return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetTenant(fieldName string) string { - if utils.IsSliceMember([]string{utils.TENANT, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.TENANT, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Tenant } if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value @@ -620,7 +620,7 @@ func (storedCdr *StoredCdr) GetTenant(fieldName string) string { return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetReqType(fieldName string) string { - if utils.IsSliceMember([]string{utils.REQTYPE, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.REQTYPE, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.ReqType } if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value @@ -629,7 +629,7 @@ func (storedCdr *StoredCdr) GetReqType(fieldName string) string { return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetSetupTime(fieldName, timezone string) (time.Time, error) { - if utils.IsSliceMember([]string{utils.SETUP_TIME, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.SETUP_TIME, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.SetupTime, nil } var sTimeVal string @@ -641,7 +641,7 @@ func (storedCdr *StoredCdr) GetSetupTime(fieldName, timezone string) (time.Time, return utils.ParseTimeDetectLayout(sTimeVal, timezone) } func (storedCdr *StoredCdr) GetAnswerTime(fieldName, timezone string) (time.Time, error) { - if utils.IsSliceMember([]string{utils.ANSWER_TIME, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.ANSWER_TIME, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.AnswerTime, nil } var aTimeVal string @@ -656,7 +656,7 @@ func (storedCdr *StoredCdr) GetEndTime(fieldName, timezone string) (time.Time, e return storedCdr.AnswerTime.Add(storedCdr.Usage), nil } func (storedCdr *StoredCdr) GetDuration(fieldName string) (time.Duration, error) { - if utils.IsSliceMember([]string{utils.USAGE, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.USAGE, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Usage, nil } var durVal string @@ -668,7 +668,7 @@ func (storedCdr *StoredCdr) GetDuration(fieldName string) (time.Duration, error) return utils.ParseDurationWithSecs(durVal) } func (storedCdr *StoredCdr) GetPdd(fieldName string) (time.Duration, error) { - if utils.IsSliceMember([]string{utils.PDD, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.PDD, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Pdd, nil } var pddVal string @@ -680,19 +680,19 @@ func (storedCdr *StoredCdr) GetPdd(fieldName string) (time.Duration, error) { return utils.ParseDurationWithSecs(pddVal) } func (storedCdr *StoredCdr) GetSupplier(fieldName string) string { - if utils.IsSliceMember([]string{utils.SUPPLIER, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.SUPPLIER, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.Supplier } return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetDisconnectCause(fieldName string) string { - if utils.IsSliceMember([]string{utils.DISCONNECT_CAUSE, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.DISCONNECT_CAUSE, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.DisconnectCause } return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } func (storedCdr *StoredCdr) GetOriginatorIP(fieldName string) string { - if utils.IsSliceMember([]string{utils.CDRHOST, utils.META_DEFAULT}, fieldName) { + if utils.IsSliceMember([]string{utils.CDRHOST, utils.META_DEFAULT, ""}, fieldName) { return storedCdr.CdrHost } return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) diff --git a/sessionmanager/smgeneric.go b/sessionmanager/smgeneric.go index f6f956ab6..275107608 100644 --- a/sessionmanager/smgeneric.go +++ b/sessionmanager/smgeneric.go @@ -199,7 +199,6 @@ func (self *SMGeneric) SessionEnd(gev SMGenericEvent, clnt *rpc2.Client) error { func (self *SMGeneric) ProcessCdr(gev SMGenericEvent) error { var reply string - utils.Logger.Debug(fmt.Sprintf("SMGeneric.ProcessCdr: %+v", gev)) if err := self.cdrsrv.ProcessCdr(gev.AsStoredCdr(self.cgrCfg, self.timezone), &reply); err != nil { return err } From 0328f065bdefa17d05e4738e4daa4484a98faa25 Mon Sep 17 00:00:00 2001 From: Brice Heppner Date: Mon, 30 Nov 2015 12:08:16 +0000 Subject: [PATCH 24/30] Detect active single-leg channels --- sessionmanager/fssessionmanager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 47a728fbd..59a04d69e 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -376,7 +376,7 @@ func (sm *FSSessionManager) SyncSessions() error { } var stillActive bool for _, fsAChan := range aChans { - if fsAChan["call_uuid"] == session.eventStart.GetUUID() { // Channel still active + if fsAChan["call_uuid"] == session.eventStart.GetUUID() || (fsAChan["call_uuid"] == "" && fsAChan["uuid"] == session.eventStart.GetUUID()) { // Channel still active stillActive = true break } From 52ed1f2708588056af532bc8b0c4bbbf11286814 Mon Sep 17 00:00:00 2001 From: Brice Heppner Date: Mon, 30 Nov 2015 12:37:44 +0000 Subject: [PATCH 25/30] Sign CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e71853316..82e27fa22 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -39,6 +39,7 @@ information, please see the [`CONTRIBUTING.md`](CONTRIBUTING.md) file. | @nikbyte | Nick Altmann | | @kjcsb1 | Cameron Beattie | | @rinor | Rinor Hoxha | +| @bhepp | Brice Heppner | From 18766f82811df3d9c90280eb0da094dc5d4dc47f Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 30 Nov 2015 14:33:47 +0100 Subject: [PATCH 26/30] Diameter tests for Account balance and CDR generation --- agents/dmtagent_it_test.go | 91 +++++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index c9e5fb8e0..2a7992d64 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -20,7 +20,6 @@ package agents import ( "flag" - "fmt" "net/rpc" "net/rpc/jsonrpc" "path" @@ -174,6 +173,7 @@ func TestDmtAgentTPFromFolder(t *testing.T) { time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups } +// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:47:26Z"' func TestDmtAgentSendCCRInit(t *testing.T) { if !*testIntegration { return @@ -185,9 +185,9 @@ func TestDmtAgentSendCCRInit(t *testing.T) { } cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", - Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", + Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", Supplier: "SUPPL1", SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, - Usage: time.Duration(0) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + Usage: time.Duration(300) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) @@ -199,17 +199,26 @@ func TestDmtAgentSendCCRInit(t *testing.T) { t.Error(err) } time.Sleep(time.Duration(100) * time.Millisecond) + var acnt *engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} + eAcntVal := 9.5 + if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) + } } +// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:52:26Z"' func TestDmtAgentSendCCRUpdate(t *testing.T) { if !*testIntegration { return } cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", - Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", + Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", Supplier: "SUPPL1", SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, - Usage: time.Duration(610) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + Usage: time.Duration(600) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) @@ -217,11 +226,49 @@ func TestDmtAgentSendCCRUpdate(t *testing.T) { if err != nil { t.Error(err) } - fmt.Printf("Will send out message: %+v\n", ccr) if err := dmtClient.SendMessage(m); err != nil { t.Error(err) } time.Sleep(time.Duration(100) * time.Millisecond) + var acnt *engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} + eAcntVal := 9.25 + if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + +// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:57:26Z"' +func TestDmtAgentSendCCRUpdate2(t *testing.T) { + if !*testIntegration { + return + } + cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, + AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", + Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", Supplier: "SUPPL1", + SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, + Usage: time.Duration(900) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + } + ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, + daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) + m, err := ccr.AsDiameterMessage() + if err != nil { + t.Error(err) + } + if err := dmtClient.SendMessage(m); err != nil { + t.Error(err) + } + time.Sleep(time.Duration(100) * time.Millisecond) + var acnt *engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} + eAcntVal := 9.0 + if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) + } } func TestDmtAgentSendCCRTerminate(t *testing.T) { @@ -230,7 +277,7 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) { } cdr := &engine.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", - Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1", + Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", Supplier: "SUPPL1", SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, Usage: time.Duration(610) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } @@ -244,6 +291,36 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) { t.Error(err) } time.Sleep(time.Duration(100) * time.Millisecond) + var acnt *engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} + eAcntVal := 8.4832 + if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { + t.Error(err) + } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != 8.4832 { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 + t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + +func TestDmtAgentCdrs(t *testing.T) { + if !*testIntegration { + return + } + var cdrs []*engine.ExternalCdr + req := utils.RpcCdrsFilter{RunIds: []string{utils.META_DEFAULT}} + if err := apierRpc.Call("ApierV2.GetCdrs", req, &cdrs); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if len(cdrs) != 1 { + t.Error("Unexpected number of CDRs returned: ", len(cdrs)) + } else { + if cdrs[0].Usage != "610" { + t.Errorf("Unexpected CDR Usage received, cdr: %+v ", cdrs[0]) + } + if cdrs[0].Cost != 0.7584 { + if cdrs[0].Usage != "610" { + t.Errorf("Unexpected CDR Cost received, cdr: %+v ", cdrs[0]) + } + } + } } func TestDmtAgentStopEngine(t *testing.T) { From add4d19ccc9597a7788e40504f1250e77c2a0845 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 30 Nov 2015 15:01:54 +0100 Subject: [PATCH 27/30] Fix diameter integration tests --- agents/dmtagent_it_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 2a7992d64..6e797ddef 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -187,7 +187,7 @@ func TestDmtAgentSendCCRInit(t *testing.T) { AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", Supplier: "SUPPL1", SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, - Usage: time.Duration(300) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + Usage: time.Duration(0) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) @@ -218,7 +218,7 @@ func TestDmtAgentSendCCRUpdate(t *testing.T) { AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", Supplier: "SUPPL1", SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, - Usage: time.Duration(600) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + Usage: time.Duration(300) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) @@ -249,7 +249,7 @@ func TestDmtAgentSendCCRUpdate2(t *testing.T) { AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", Supplier: "SUPPL1", SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID, - Usage: time.Duration(900) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + Usage: time.Duration(600) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) @@ -293,10 +293,10 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) { time.Sleep(time.Duration(100) * time.Millisecond) var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 8.4832 + eAcntVal := 9.2416 if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) - } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != 8.4832 { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 + } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) } } From b779c4685c78f69fccf3abf3fbfd7df56609a38d Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 30 Nov 2015 14:35:59 +0200 Subject: [PATCH 28/30] added actions console command --- console/actions.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 console/actions.go diff --git a/console/actions.go b/console/actions.go new file mode 100644 index 000000000..3365a87b6 --- /dev/null +++ b/console/actions.go @@ -0,0 +1,60 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) 2012-2015 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 console + +import "github.com/cgrates/cgrates/utils" + +func init() { + c := &CmdGetActions{ + name: "actions", + rpcMethod: "ApierV2.GetActions", + rpcParams: "", + } + commands[c.Name()] = c + c.CommandExecuter = &CommandExecuter{c} +} + +// Commander implementation +type CmdGetActions struct { + name string + rpcMethod string + rpcParams string + *CommandExecuter +} + +func (self *CmdGetActions) Name() string { + return self.name +} + +func (self *CmdGetActions) RpcMethod() string { + return self.rpcMethod +} + +func (self *CmdGetActions) RpcParams(reset bool) interface{} { + return self.rpcParams +} + +func (self *CmdGetActions) PostprocessRpcParams() error { + return nil +} + +func (self *CmdGetActions) RpcResult() interface{} { + a := make([]*utils.TPAction, 0) + return &a +} From f57ebb16d2da42bb6e6257d9aca6789079ad4f70 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 30 Nov 2015 19:37:43 +0200 Subject: [PATCH 29/30] create incerements for getcost as well --- console/actions.go | 6 ++++-- engine/account.go | 3 ++- engine/account_test.go | 3 +++ engine/calldesc.go | 1 + engine/calldesc_test.go | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/console/actions.go b/console/actions.go index 3365a87b6..25e9af0e5 100644 --- a/console/actions.go +++ b/console/actions.go @@ -24,7 +24,6 @@ func init() { c := &CmdGetActions{ name: "actions", rpcMethod: "ApierV2.GetActions", - rpcParams: "", } commands[c.Name()] = c c.CommandExecuter = &CommandExecuter{c} @@ -34,7 +33,7 @@ func init() { type CmdGetActions struct { name string rpcMethod string - rpcParams string + rpcParams *StringWrapper *CommandExecuter } @@ -47,6 +46,9 @@ func (self *CmdGetActions) RpcMethod() string { } func (self *CmdGetActions) RpcParams(reset bool) interface{} { + if reset || self.rpcParams == nil { + self.rpcParams = &StringWrapper{} + } return self.rpcParams } diff --git a/engine/account.go b/engine/account.go index 555545efc..dc7c5c5c4 100644 --- a/engine/account.go +++ b/engine/account.go @@ -365,7 +365,7 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo cc.Timespans = append(cc.Timespans, leftCC.Timespans...) } - //log.Printf("HERE: %+v %d", leftCC) + //log.Printf("HERE: %+v", leftCC) if leftCC.Cost > 0 && goNegative { initialLength := len(cc.Timespans) cc.Timespans = append(cc.Timespans, leftCC.Timespans...) @@ -379,6 +379,7 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo if len(leftCC.Timespans) > 0 && leftCC.Cost > 0 && !ub.AllowNegative && !dryRun { utils.Logger.Err(fmt.Sprintf(" Going negative on account %s with AllowNegative: false", cd.GetAccountKey())) } + leftCC.Timespans.Decompress() for _, ts := range leftCC.Timespans { if ts.Increments == nil { ts.createIncrementsSlice() diff --git a/engine/account_test.go b/engine/account_test.go index a272d0251..0cdcc7c10 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -143,6 +143,9 @@ func TestGetSpecialPricedSeconds(t *testing.T) { expected := 20 * time.Second if credit != 0 || seconds != expected || len(bucketList) != 2 || bucketList[0].Weight < bucketList[1].Weight { t.Log(seconds, credit, bucketList) + for _, b := range bucketList { + t.Logf("Balance: %+v", b) + } t.Errorf("Expected %v was %v", expected, seconds) } } diff --git a/engine/calldesc.go b/engine/calldesc.go index b98e3def8..96dacd589 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -503,6 +503,7 @@ func (cd *CallDescriptor) getCost() (*CallCost, error) { cost := 0.0 for i, ts := range timespans { + ts.createIncrementsSlice() // only add connect fee if this is the first/only call cost request //log.Printf("Interval: %+v", ts.RateInterval.Timing) if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 75926480f..2e8744fcd 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -574,7 +574,7 @@ func TestGetCostRoundingIssue(t *testing.T) { MaxCostSoFar: 0, } cc, err := cd.GetCost() - expected := 0.17 + expected := 0.39 if cc.Cost != expected || err != nil { t.Log(utils.ToIJSON(cc)) t.Errorf("Expected %v was %+v", expected, cc) From 2ea36b56cde211e0895ee1d018cfcc623a0060e1 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 30 Nov 2015 19:51:12 +0200 Subject: [PATCH 30/30] updated local tests --- general_tests/tutorial_local_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index 434fa6461..5d4beec49 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -193,7 +193,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 0.6417 { // 0.01 first minute, 0.04 25 seconds with RT_20CNT + } else if cc.Cost != 0.6425 { // 0.01 first minute, 0.04 25 seconds with RT_20CNT t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") @@ -229,7 +229,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 1.3 { + } else if cc.Cost != 1.3002 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") @@ -265,7 +265,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 1.3 { + } else if cc.Cost != 1.3002 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart = time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC)