diff --git a/apier/cdre.go b/apier/cdre.go index 7018621eb..3628aa67d 100644 --- a/apier/cdre.go +++ b/apier/cdre.go @@ -59,7 +59,8 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E } } else { exportTemplate, _ = config.NewDefaultCdreConfig() - if contentFlds, err := config.NewCdreCdrFieldsFromIds(strings.Split(*attr.ExportTemplate, string(utils.CSV_SEP))...); err != nil { + if contentFlds, err := config.NewCdreCdrFieldsFromIds(exportTemplate.CdrFormat == utils.CDRE_FIXED_WIDTH, + strings.Split(*attr.ExportTemplate, string(utils.CSV_SEP))...); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } else { exportTemplate.ContentFields = contentFlds diff --git a/cdre/cdrexporter.go b/cdre/cdrexporter.go index ef6201a99..842bb7deb 100644 --- a/cdre/cdrexporter.go +++ b/cdre/cdrexporter.go @@ -241,11 +241,9 @@ func (cdre *CdrExporter) composeHeader() error { return err } fmtOut := outVal - if cdre.cdrFormat == utils.CDRE_FIXED_WIDTH { - if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { - engine.Logger.Err(fmt.Sprintf(" Cannot export CDR header, field %s, error: %s", cfgFld.Name, err.Error())) - return err - } + if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + engine.Logger.Err(fmt.Sprintf(" Cannot export CDR header, field %s, error: %s", cfgFld.Name, err.Error())) + return err } cdre.header = append(cdre.header, fmtOut) } @@ -272,11 +270,9 @@ func (cdre *CdrExporter) composeTrailer() error { return err } fmtOut := outVal - if cdre.cdrFormat == utils.CDRE_FIXED_WIDTH { - if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { - engine.Logger.Err(fmt.Sprintf(" Cannot export CDR trailer, field: %s, error: %s", cfgFld.Name, err.Error())) - return err - } + if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + engine.Logger.Err(fmt.Sprintf(" Cannot export CDR trailer, field: %s, error: %s", cfgFld.Name, err.Error())) + return err } cdre.trailer = append(cdre.trailer, fmtOut) } @@ -338,11 +334,9 @@ func (cdre *CdrExporter) processCdr(cdr *utils.StoredCdr) error { return err } fmtOut := outVal - if cdre.cdrFormat == utils.CDRE_FIXED_WIDTH { - if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { - engine.Logger.Err(fmt.Sprintf(" Cannot export CDR with cgrid: %s, runid: %s, fieldName: %s, fieldValue: %s, error: %s", cdr.CgrId, cdr.MediationRunId, cfgFld.Name, outVal, err.Error())) - return err - } + if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + engine.Logger.Err(fmt.Sprintf(" Cannot export CDR with cgrid: %s, runid: %s, fieldName: %s, fieldValue: %s, error: %s", cdr.CgrId, cdr.MediationRunId, cfgFld.Name, outVal, err.Error())) + return err } cdrRow[idx] += fmtOut } @@ -362,8 +356,10 @@ func (cdre *CdrExporter) processCdr(cdr *utils.StoredCdr) error { if !utils.IsSliceMember([]string{utils.DATA, utils.SMS}, cdr.TOR) { // Only count duration for non data cdrs cdre.totalDuration += cdr.Usage } - cdre.totalCost += cdr.Cost - cdre.totalCost = utils.Round(cdre.totalCost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) + if cdr.Cost != -1 { + cdre.totalCost += cdr.Cost + cdre.totalCost = utils.Round(cdre.totalCost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) + } if cdre.firstExpOrderId > cdr.OrderId || cdre.firstExpOrderId == 0 { cdre.firstExpOrderId = cdr.OrderId } diff --git a/cdre/libcdre.go b/cdre/libcdre.go index 38d507b7c..d1569720d 100644 --- a/cdre/libcdre.go +++ b/cdre/libcdre.go @@ -35,6 +35,9 @@ func FmtFieldWidth(source string, width int, strip, padding string, mandatory bo 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 } diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index 118078e34..c91c0bce3 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -97,7 +97,7 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD var err error for i := 0; i < cfg.MediatorRaterReconnects; i++ { - client, err = rpcclient.NewRpcClient("tcp", cfg.MediatorRater, 0, 3, utils.GOB) + client, err = rpcclient.NewRpcClient("tcp", cfg.MediatorRater, 0, cfg.MediatorRaterReconnects, utils.GOB) if err == nil { //Connected so no need to reiterate break } @@ -150,7 +150,7 @@ func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage var err error for i := 0; i < cfg.SMRaterReconnects; i++ { - client, err = rpcclient.NewRpcClient("tcp", cfg.SMRater, 0, 3, utils.GOB) + client, err = rpcclient.NewRpcClient("tcp", cfg.SMRater, 0, cfg.SMRaterReconnects, utils.GOB) if err == nil { //Connected so no need to reiterate break } diff --git a/config/cdreconfig.go b/config/cdreconfig.go index 7fea319d0..1df24a9fe 100644 --- a/config/cdreconfig.go +++ b/config/cdreconfig.go @@ -24,15 +24,16 @@ import ( ) // Converts a list of field identifiers into proper CDR field content -func NewCdreCdrFieldsFromIds(fldsIds ...string) ([]*CdreCdrField, error) { +func NewCdreCdrFieldsFromIds(withFixedWith bool, fldsIds ...string) ([]*CdreCdrField, error) { cdrFields := make([]*CdreCdrField, len(fldsIds)) for idx, fldId := range fldsIds { if parsedRsr, err := utils.NewRSRField(fldId); err != nil { return nil, err } else { cdrFld := &CdreCdrField{Name: fldId, Type: utils.CDRFIELD, Value: fldId, valueAsRsrField: parsedRsr} - if err := cdrFld.setDefaultFixedWidthProperties(); err != nil { // Set default fixed width properties to be used later if needed + if err := cdrFld.setDefaultFieldProperties(withFixedWith); err != nil { // Set default fixed width properties to be used later if needed return nil, err + } cdrFields[idx] = cdrFld } @@ -73,7 +74,7 @@ func (cdreCfg *CdreConfig) setDefaults() error { cdreCfg.MaskDestId = "" cdreCfg.MaskLength = 0 cdreCfg.ExportDir = "/var/log/cgrates/cdre" - if flds, err := NewCdreCdrFieldsFromIds(utils.CGRID, utils.MEDI_RUNID, utils.TOR, utils.ACCID, utils.REQTYPE, utils.DIRECTION, utils.TENANT, + if flds, err := NewCdreCdrFieldsFromIds(false, utils.CGRID, utils.MEDI_RUNID, utils.TOR, utils.ACCID, utils.REQTYPE, utils.DIRECTION, utils.TENANT, utils.CATEGORY, utils.ACCOUNT, utils.SUBJECT, utils.DESTINATION, utils.SETUP_TIME, utils.ANSWER_TIME, utils.USAGE, utils.COST); err != nil { return err } else { @@ -100,125 +101,142 @@ func (cdrField *CdreCdrField) ValueAsRSRField() *utils.RSRField { } // Should be called on .fwv configuration without providing default values for fixed with parameters -func (cdrField *CdreCdrField) setDefaultFixedWidthProperties() error { +func (cdrField *CdreCdrField) setDefaultFieldProperties(fixedWidth bool) error { if cdrField.valueAsRsrField == nil { return errors.New("Missing valueAsRsrField") } switch cdrField.valueAsRsrField.Id { case utils.CGRID: - cdrField.Width = 40 - cdrField.Strip = "" - cdrField.Padding = "" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 40 + } case utils.ORDERID: - cdrField.Width = 11 - cdrField.Strip = "" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 11 + cdrField.Padding = "left" + } case utils.TOR: - cdrField.Width = 6 - cdrField.Strip = "" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 6 + cdrField.Padding = "left" + } case utils.ACCID: - cdrField.Width = 36 - cdrField.Strip = "left" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 36 + cdrField.Strip = "left" + cdrField.Padding = "left" + } case utils.CDRHOST: - cdrField.Width = 15 - cdrField.Strip = "left" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 15 + cdrField.Strip = "left" + cdrField.Padding = "left" + } case utils.CDRSOURCE: - cdrField.Width = 15 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 15 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.REQTYPE: - cdrField.Width = 13 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 13 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.DIRECTION: - cdrField.Width = 4 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 4 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.TENANT: - cdrField.Width = 24 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 24 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.CATEGORY: - cdrField.Width = 10 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 10 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.ACCOUNT: - cdrField.Width = 24 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 24 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.SUBJECT: - cdrField.Width = 24 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 24 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.DESTINATION: - cdrField.Width = 24 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 24 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.SETUP_TIME: - cdrField.Width = 30 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "2006-01-02T15:04:05Z07:00" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 30 + cdrField.Strip = "xright" + cdrField.Padding = "left" + cdrField.Layout = "2006-01-02T15:04:05Z07:00" + } case utils.ANSWER_TIME: - cdrField.Width = 30 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "2006-01-02T15:04:05Z07:00" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 30 + cdrField.Strip = "xright" + cdrField.Padding = "left" + cdrField.Layout = "2006-01-02T15:04:05Z07:00" + } case utils.USAGE: - cdrField.Width = 30 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 30 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.MEDI_RUNID: - cdrField.Width = 20 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 20 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } case utils.COST: - cdrField.Width = 24 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = true + if fixedWidth { + cdrField.Width = 24 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } default: - cdrField.Width = 30 - cdrField.Strip = "xright" - cdrField.Padding = "left" - cdrField.Layout = "" cdrField.Mandatory = false + if fixedWidth { + cdrField.Width = 30 + cdrField.Strip = "xright" + cdrField.Padding = "left" + } } return nil } diff --git a/config/cdreconfig_test.go b/config/cdreconfig_test.go index afbe55b76..18e65a8cd 100644 --- a/config/cdreconfig_test.go +++ b/config/cdreconfig_test.go @@ -49,7 +49,7 @@ func TestCdreCfgNewCdreCdrFieldsFromIds(t *testing.T) { valueAsRsrField: &utils.RSRField{Id: "extra1"}, }, } - if cdreFlds, err := NewCdreCdrFieldsFromIds(utils.CGRID, "extra1"); err != nil { + if cdreFlds, err := NewCdreCdrFieldsFromIds(true, utils.CGRID, "extra1"); err != nil { t.Error(err) } else if !reflect.DeepEqual(expectedFlds, cdreFlds) { t.Errorf("Expected: %v, received: %v", expectedFlds, cdreFlds) @@ -86,7 +86,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.CGRID, Type: utils.CDRFIELD, Value: utils.CGRID, - Width: 40, Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.CGRID}, }, @@ -94,9 +93,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.MEDI_RUNID, Type: utils.CDRFIELD, Value: utils.MEDI_RUNID, - Width: 20, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID}, }, @@ -104,8 +100,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.TOR, Type: utils.CDRFIELD, Value: utils.TOR, - Width: 6, - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.TOR}, }, @@ -113,9 +107,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.ACCID, Type: utils.CDRFIELD, Value: utils.ACCID, - Width: 36, - Strip: "left", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.ACCID}, }, @@ -123,9 +114,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.REQTYPE, Type: utils.CDRFIELD, Value: utils.REQTYPE, - Width: 13, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE}, }, @@ -133,9 +121,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.DIRECTION, Type: utils.CDRFIELD, Value: utils.DIRECTION, - Width: 4, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION}, }, @@ -143,9 +128,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.TENANT, Type: utils.CDRFIELD, Value: utils.TENANT, - Width: 24, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.TENANT}, }, @@ -153,9 +135,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.CATEGORY, Type: utils.CDRFIELD, Value: utils.CATEGORY, - Width: 10, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY}, }, @@ -163,9 +142,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.ACCOUNT, Type: utils.CDRFIELD, Value: utils.ACCOUNT, - Width: 24, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT}, }, @@ -173,9 +149,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.SUBJECT, Type: utils.CDRFIELD, Value: utils.SUBJECT, - Width: 24, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT}, }, @@ -183,9 +156,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.DESTINATION, Type: utils.CDRFIELD, Value: utils.DESTINATION, - Width: 24, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION}, }, @@ -193,10 +163,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.SETUP_TIME, Type: utils.CDRFIELD, Value: utils.SETUP_TIME, - Width: 30, - Strip: "xright", - Padding: "left", - Layout: "2006-01-02T15:04:05Z07:00", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME}, }, @@ -204,10 +170,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.ANSWER_TIME, Type: utils.CDRFIELD, Value: utils.ANSWER_TIME, - Width: 30, - Strip: "xright", - Padding: "left", - Layout: "2006-01-02T15:04:05Z07:00", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME}, }, @@ -215,9 +177,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.USAGE, Type: utils.CDRFIELD, Value: utils.USAGE, - Width: 30, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.USAGE}, }, @@ -225,9 +184,6 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { Name: utils.COST, Type: utils.CDRFIELD, Value: utils.COST, - Width: 24, - Strip: "xright", - Padding: "left", Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.COST}, }, @@ -239,7 +195,7 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) { } } -func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { +func TestCdreCfgSetDefaultFieldProperties(t *testing.T) { cdreCdrFld := &CdreCdrField{ valueAsRsrField: &utils.RSRField{Id: utils.CGRID}, } @@ -248,7 +204,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.CGRID}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -262,7 +218,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.ORDERID}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -276,7 +232,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.TOR}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -291,7 +247,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.ACCID}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -306,7 +262,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.CDRHOST}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -321,7 +277,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.CDRSOURCE}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -336,7 +292,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -351,7 +307,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -366,7 +322,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.TENANT}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -381,7 +337,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -396,7 +352,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -411,7 +367,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -426,7 +382,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -442,7 +398,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -458,7 +414,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -473,7 +429,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.USAGE}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -488,7 +444,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -503,7 +459,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: true, valueAsRsrField: &utils.RSRField{Id: utils.COST}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) @@ -518,7 +474,7 @@ func TestCdreCfgSetDefaultFixedWidthProperties(t *testing.T) { Mandatory: false, valueAsRsrField: &utils.RSRField{Id: "extra_1"}, } - if err := cdreCdrFld.setDefaultFixedWidthProperties(); err != nil { + if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) { t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld) diff --git a/config/config.go b/config/config.go index 44d13668a..53825d4d6 100644 --- a/config/config.go +++ b/config/config.go @@ -423,7 +423,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { cfg.CdreDefaultInstance = xmlTemplates[exportTemplate[len(utils.XML_PROFILE_PREFIX):]].AsCdreConfig() } } else { // Not loading out of template - if flds, err := NewCdreCdrFieldsFromIds(strings.Split(exportTemplate, string(utils.CSV_SEP))...); err != nil { + if flds, err := NewCdreCdrFieldsFromIds(cfg.CdreDefaultInstance.CdrFormat == utils.CDRE_FIXED_WIDTH, + strings.Split(exportTemplate, string(utils.CSV_SEP))...); err != nil { return nil, err } else { cfg.CdreDefaultInstance.ContentFields = flds diff --git a/config/config_test.go b/config/config_test.go index 3bf32117b..22a5303fb 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -214,7 +214,7 @@ func TestConfigFromFile(t *testing.T) { MaskDestId: "test", MaskLength: 99, ExportDir: "test"} - eCfg.CdreDefaultInstance.ContentFields, _ = NewCdreCdrFieldsFromIds("test") + eCfg.CdreDefaultInstance.ContentFields, _ = NewCdreCdrFieldsFromIds(false, "test") eCfg.CdrcEnabled = true eCfg.CdrcCdrs = "test" eCfg.CdrcRunDelay = time.Duration(99) * time.Second @@ -308,11 +308,11 @@ cdr_format = csv export_template = cgrid,mediation_runid,accid `) expectedFlds := []*CdreCdrField{ - &CdreCdrField{Name: "cgrid", Type: utils.CDRFIELD, Value: "cgrid", valueAsRsrField: &utils.RSRField{Id: "cgrid"}, Width: 40, Mandatory: true}, + &CdreCdrField{Name: "cgrid", Type: utils.CDRFIELD, Value: "cgrid", valueAsRsrField: &utils.RSRField{Id: "cgrid"}, Mandatory: true}, &CdreCdrField{Name: "mediation_runid", Type: utils.CDRFIELD, Value: "mediation_runid", valueAsRsrField: &utils.RSRField{Id: "mediation_runid"}, - Width: 20, Strip: "xright", Padding: "left", Mandatory: true}, + Mandatory: true}, &CdreCdrField{Name: "accid", Type: utils.CDRFIELD, Value: "accid", valueAsRsrField: &utils.RSRField{Id: "accid"}, - Width: 36, Strip: "left", Padding: "left", Mandatory: true}, + Mandatory: true}, } expCdreCfg := &CdreConfig{CdrFormat: utils.CSV, CostRoundingDecimals: -1, ExportDir: "/var/log/cgrates/cdre", ContentFields: expectedFlds} if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil { @@ -326,9 +326,9 @@ export_template = cgrid,~effective_caller_id_number:s/(\d+)/+$1/ `) rsrField, _ := utils.NewRSRField(`~effective_caller_id_number:s/(\d+)/+$1/`) expectedFlds = []*CdreCdrField{ - &CdreCdrField{Name: "cgrid", Type: utils.CDRFIELD, Value: "cgrid", valueAsRsrField: &utils.RSRField{Id: "cgrid"}, Width: 40, Mandatory: true}, - &CdreCdrField{Name: `~effective_caller_id_number:s/(\d+)/+$1/`, Type: utils.CDRFIELD, Value: `~effective_caller_id_number:s/(\d+)/+$1/`, valueAsRsrField: rsrField, - Width: 30, Strip: "xright", Padding: "left", Mandatory: false}} + &CdreCdrField{Name: "cgrid", Type: utils.CDRFIELD, Value: "cgrid", valueAsRsrField: &utils.RSRField{Id: "cgrid"}, Mandatory: true}, + &CdreCdrField{Name: `~effective_caller_id_number:s/(\d+)/+$1/`, Type: utils.CDRFIELD, Value: `~effective_caller_id_number:s/(\d+)/+$1/`, + valueAsRsrField: rsrField, Mandatory: false}} expCdreCfg.ContentFields = expectedFlds if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil { t.Error("Could not parse the config", err.Error()) diff --git a/engine/account.go b/engine/account.go index caf261cf0..6e82c6e7f 100644 --- a/engine/account.go +++ b/engine/account.go @@ -344,12 +344,12 @@ func (ub *Account) executeActionTriggers(a *Action) { if uc.BalanceType == at.BalanceType { for _, mb := range uc.Balances { if strings.Contains(at.ThresholdType, "*max") { - if mb.MatchDestination(at.DestinationId) && mb.Value >= at.ThresholdValue { + if mb.MatchActionTrigger(at) && mb.Value >= at.ThresholdValue { // run the actions at.Execute(ub) } } else { //MIN - if mb.MatchDestination(at.DestinationId) && mb.Value <= at.ThresholdValue { + if mb.MatchActionTrigger(at) && mb.Value <= at.ThresholdValue { // run the actions at.Execute(ub) } @@ -359,13 +359,16 @@ func (ub *Account) executeActionTriggers(a *Action) { } } else { // BALANCE for _, b := range ub.BalanceMap[at.BalanceType+at.Direction] { + if !b.dirty { // do not check clean balances + continue + } if strings.Contains(at.ThresholdType, "*max") { - if b.MatchDestination(at.DestinationId) && b.Value >= at.ThresholdValue { + if b.MatchActionTrigger(at) && b.Value >= at.ThresholdValue { // run the actions at.Execute(ub) } } else { //MIN - if b.MatchDestination(at.DestinationId) && b.Value <= at.ThresholdValue { + if b.MatchActionTrigger(at) && b.Value <= at.ThresholdValue { // run the actions at.Execute(ub) } diff --git a/engine/action_trigger.go b/engine/action_trigger.go index b2a99c0b4..1cfe025f8 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -33,15 +33,17 @@ type ActionTrigger struct { Direction string ThresholdType string //*min_counter, *max_counter, *min_balance, *max_balance // stats: *min_asr, *max_asr, *min_acd, *max_acd, *min_acc, *max_acc - ThresholdValue float64 - Recurrent bool // reset eexcuted flag each run - MinSleep time.Duration // Minimum duration between two executions in case of recurrent triggers - DestinationId string - Weight float64 - ActionsId string - Executed bool - MinQueuedItems int // Trigger actions only if this number is hit (stats only) - lastExecutionTime time.Time + ThresholdValue float64 + Recurrent bool // reset eexcuted flag each run + MinSleep time.Duration // Minimum duration between two executions in case of recurrent triggers + DestinationId string // filter for balance + BalanceWeight float64 // filter for balance + BalanceExpirationDate time.Time // filter for balance + Weight float64 + ActionsId string + Executed bool + MinQueuedItems int // Trigger actions only if this number is hit (stats only) + lastExecutionTime time.Time } func (at *ActionTrigger) Execute(ub *Account) (err error) { @@ -51,7 +53,7 @@ func (at *ActionTrigger) Execute(ub *Account) (err error) { } at.lastExecutionTime = time.Now() if ub.Disabled { - return fmt.Errorf("User %s is disabled", ub.Id) + return fmt.Errorf("User %s is disabled and there are triggers in action!", ub.Id) } // does NOT need to Lock() because it is triggered from a method that took the Lock var aac Actions diff --git a/engine/actions_test.go b/engine/actions_test.go index 048844c11..6d1cda925 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -827,7 +827,7 @@ func TestActionResetCounterMinutes(t *testing.T) { CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceType: CREDIT, Balances: BalanceChain{&Balance{Value: 1}}}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceType: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceType: CREDIT, ThresholdType: "*max_counter", ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceType: MINUTES} resetCounterAction(ub, a) @@ -840,14 +840,14 @@ func TestActionResetCounterMinutes(t *testing.T) { for _, b := range ub.UnitCounters[1].Balances { t.Logf("B: %+v", b) } - t.Error("Reset counters action failed!", ub.UnitCounters[1]) + t.Errorf("Reset counters action failed: %+v", ub) } if len(ub.UnitCounters) < 2 || len(ub.UnitCounters[1].Balances) < 1 { t.FailNow() } mb := ub.UnitCounters[1].Balances[0] if mb.Weight != 20 || mb.Value != 0 || mb.DestinationId != "NAT" { - t.Errorf("Balance cloned incorrectly: %v!", mb) + t.Errorf("Balance cloned incorrectly: %+v!", mb) } } diff --git a/engine/balances.go b/engine/balances.go index 8f8070e59..66851282f 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -74,6 +74,20 @@ func (b *Balance) MatchDestination(destinationId string) bool { return !b.HasDestination() || b.DestinationId == destinationId } +func (b *Balance) MatchActionTrigger(at *ActionTrigger) bool { + matchesExpirationDate := true + if !at.BalanceExpirationDate.IsZero() { + matchesExpirationDate = (at.BalanceExpirationDate.Equal(b.ExpirationDate)) + } + matchesWeight := true + if at.BalanceWeight > 0 { + matchesWeight = (at.BalanceWeight == b.Weight) + } + return b.MatchDestination(at.DestinationId) && + matchesExpirationDate && + matchesWeight +} + func (b *Balance) Clone() *Balance { return &Balance{ Uuid: b.Uuid, diff --git a/engine/units_counter.go b/engine/units_counter.go index 4bc76ac12..879e8699c 100644 --- a/engine/units_counter.go +++ b/engine/units_counter.go @@ -19,6 +19,8 @@ along with this program. If not, see package engine import ( + "strings" + "github.com/cgrates/cgrates/cache2go" "github.com/cgrates/cgrates/utils" ) @@ -36,6 +38,10 @@ type UnitsCounter struct { func (uc *UnitsCounter) initBalances(ats []*ActionTrigger) { uc.Balances = BalanceChain{&Balance{}} // general balance for _, at := range ats { + if !strings.Contains(at.ThresholdType, "counter") { + // only get actions fo counter type action triggers + continue + } acs, err := accountingStorage.GetActions(at.ActionsId, false) if err != nil { continue diff --git a/utils/rsrfield.go b/utils/rsrfield.go index 20ceaaac5..4f4fa604b 100644 --- a/utils/rsrfield.go +++ b/utils/rsrfield.go @@ -48,17 +48,17 @@ func NewRSRField(fldStr string) (*RSRField, error) { spltRgxp := regexp.MustCompile(`:s\/`) spltRules := spltRgxp.Split(fldStr, -1) if len(spltRules) < 2 { - return nil, fmt.Errorf("Invalid Search&Replace field rule. %s", fldStr) + return nil, fmt.Errorf("Invalid Split of Search&Replace field rule. %s", fldStr) } rsrField := &RSRField{Id: spltRules[0][1:]} // Original id in form ~hdr_name - rulesRgxp := regexp.MustCompile(`(?:(.+[^\\])\/(.+[^\\])*\/){1,}`) + rulesRgxp := regexp.MustCompile(`(?:(.+[^\\])\/(.*[^\\])*\/){1,}`) for _, ruleStr := range spltRules[1:] { // :s/ already removed through split allMatches := rulesRgxp.FindStringSubmatch(ruleStr) if len(allMatches) != 3 { - return nil, fmt.Errorf("Invalid Search&Replace field rule. %s", fldStr) + return nil, fmt.Errorf("Not enough members in Search&Replace, ruleStr: %s, matches: %v, ", ruleStr, allMatches) } if srRegexp, err := regexp.Compile(allMatches[1]); err != nil { - return nil, fmt.Errorf("Invalid Search&Replace field rule. %s", fldStr) + return nil, fmt.Errorf("Invalid Search&Replace subfield rule: %s", allMatches[1]) } else { rsrField.RSRules = append(rsrField.RSRules, &ReSearchReplace{SearchRegexp: srRegexp, ReplaceTemplate: allMatches[2]}) } diff --git a/utils/rsrfield_test.go b/utils/rsrfield_test.go index 2eca3667d..6b9339aab 100644 --- a/utils/rsrfield_test.go +++ b/utils/rsrfield_test.go @@ -65,6 +65,9 @@ func TestNewRSRFieldIvo(t *testing.T) { } else if !reflect.DeepEqual(rsrField, expectRSRField) { t.Errorf("Unexpected RSRField received: %v", rsrField) } + if _, err := NewRSRField(`~account:s/^[A-Za-z0-9]*[c|a]\d{4}$/S/:s/^[A-Za-z0-9]*n\d{4}$/C/:s/^\d{10}$//`); err != nil { + t.Error(err) + } } func TestConvertPlusNationalAnd00(t *testing.T) {