Refactoring CDRE and CDRC configurations and functionality

This commit is contained in:
DanB
2014-10-02 19:02:23 +02:00
parent f609acd335
commit e4c9cf561a
27 changed files with 832 additions and 1549 deletions

View File

@@ -73,7 +73,7 @@ func TestCreateDirs(t *testing.T) {
if !*testLocal {
return
}
for _, pathDir := range []string{cfg.CdreDefaultInstance.ExportDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
for _, pathDir := range []string{cfg.CdreDefaultInstance.ExportDir, cfg.CdrcInstances[0].CdrInDir, cfg.CdrcInstances[0].CdrOutDir, cfg.HistoryDir} {
if err := os.RemoveAll(pathDir); err != nil {
t.Fatal("Error removing folder: ", pathDir, err)
}

View File

@@ -114,11 +114,13 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
if xmlTemplates := self.Config.XmlCfgDocument.GetCdreCfgs(expTplStr[len(utils.XML_PROFILE_PREFIX):]); xmlTemplates == nil {
return fmt.Errorf("%s:ExportTemplate", utils.ERR_NOT_FOUND)
} else {
exportTemplate = xmlTemplates[expTplStr[len(utils.XML_PROFILE_PREFIX):]].AsCdreConfig()
if exportTemplate, err = config.NewCdreConfigFromXmlCdreCfg(xmlTemplates[expTplStr[len(utils.XML_PROFILE_PREFIX):]]); err != nil {
return fmt.Errorf("%s:ExportTemplate:%s", utils.ERR_SERVER_ERROR, err.Error())
}
}
} else {
exportTemplate, _ = config.NewDefaultCdreConfig()
if contentFlds, err := config.NewCdreCdrFieldsFromIds(exportTemplate.CdrFormat == utils.CDRE_FIXED_WIDTH,
exportTemplate = config.NewDefaultCdreConfig()
if contentFlds, err := config.NewCfgCdrFieldsFromIds(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 {

View File

@@ -28,10 +28,10 @@ import (
"os"
"path"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/howeyc/fsnotify"
@@ -42,13 +42,13 @@ const (
FS_CSV = "freeswitch_csv"
)
func NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField, cdrServer *engine.CDRS) (*Cdrc, error) {
if len(csvSep) != 1 {
return nil, fmt.Errorf("Unsupported csv separator: %s", csvSep)
func NewCdrc(cdrcCfg *config.CdrcConfig, httpSkipTlsCheck bool, cdrServer *engine.CDRS) (*Cdrc, error) {
if len(cdrcCfg.FieldSeparator) != 1 {
return nil, fmt.Errorf("Unsupported csv separator: %s", cdrcCfg.FieldSeparator)
}
csvSepRune, _ := utf8.DecodeRune([]byte(csvSep))
cdrc := &Cdrc{cdrsAddress: cdrsAddress, cdrType: cdrType, cdrInDir: cdrInDir, cdrOutDir: cdrOutDir,
cdrSourceId: cdrSourceId, runDelay: runDelay, csvSep: csvSepRune, cdrFields: cdrFields, cdrServer: cdrServer}
csvSepRune, _ := utf8.DecodeRune([]byte(cdrcCfg.FieldSeparator))
cdrc := &Cdrc{cdrsAddress: cdrcCfg.CdrsAddress, cdrType: cdrcCfg.CdrType, cdrInDir: cdrcCfg.CdrInDir, cdrOutDir: cdrcCfg.CdrOutDir,
cdrSourceId: cdrcCfg.CdrSourceId, runDelay: cdrcCfg.RunDelay, csvSep: csvSepRune, cdrFields: cdrcCfg.CdrFields, httpSkipTlsCheck: httpSkipTlsCheck, cdrServer: cdrServer}
// Before processing, make sure in and out folders exist
for _, dir := range []string{cdrc.cdrInDir, cdrc.cdrOutDir} {
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
@@ -65,11 +65,12 @@ type Cdrc struct {
cdrInDir,
cdrOutDir,
cdrSourceId string
runDelay time.Duration
csvSep rune
cdrFields map[string][]*utils.RSRField
cdrServer *engine.CDRS // Reference towards internal cdrServer if that is the case
httpClient *http.Client
runDelay time.Duration
csvSep rune
cdrFields []*config.CfgCdrField
httpSkipTlsCheck bool
cdrServer *engine.CDRS // Reference towards internal cdrServer if that is the case
httpClient *http.Client
}
// When called fires up folder monitoring, either automated via inotify or manual by sleeping between processing
@@ -77,7 +78,7 @@ func (self *Cdrc) Run() error {
if self.runDelay == time.Duration(0) { // Automated via inotify
return self.trackCDRFiles()
}
// No automated, process and sleep approach
// Not automated, process and sleep approach
for {
self.processCdrDir()
time.Sleep(self.runDelay)
@@ -88,24 +89,40 @@ func (self *Cdrc) Run() error {
func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) {
storedCdr := &utils.StoredCdr{CdrHost: "0.0.0.0", CdrSource: self.cdrSourceId, ExtraFields: make(map[string]string), Cost: -1}
var err error
for cfgFieldName, cfgFieldRSRs := range self.cdrFields {
for _, cdrFldCfg := range self.cdrFields {
var fieldVal string
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cdrType) {
for _, cfgFieldRSR := range cfgFieldRSRs {
if strings.HasPrefix(cfgFieldRSR.Id, utils.STATIC_VALUE_PREFIX) {
fieldVal += cfgFieldRSR.ParseValue("PLACEHOLDER")
} else { // Dynamic value extracted using index
if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx {
return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName)
} else {
fieldVal += cfgFieldRSR.ParseValue(record[cfgFieldIdx])
if cdrFldCfg.Type == utils.CDRFIELD {
for _, cfgFieldRSR := range cdrFldCfg.Value {
if cfgFieldRSR.IsStatic() {
fieldVal += cfgFieldRSR.ParseValue("")
} else { // Dynamic value extracted using index
if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx {
return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cdrFldCfg.Tag)
} else {
fieldVal += cfgFieldRSR.ParseValue(record[cfgFieldIdx])
}
}
}
} else if cdrFldCfg.Type == utils.HTTP_POST {
var outValByte []byte
var httpAddr string
for _, rsrFld := range cdrFldCfg.Value {
httpAddr += rsrFld.ParseValue("")
}
if outValByte, err = utils.HttpJsonPost(httpAddr, self.httpSkipTlsCheck, record); err == nil {
fieldVal = string(outValByte)
if len(fieldVal) == 0 && cdrFldCfg.Mandatory {
return nil, fmt.Errorf("MandatoryIeMissing: thEmpty result for http_post field: %s", cdrFldCfg.Tag)
}
}
} else {
return nil, fmt.Errorf("Unsupported field type: %s", cdrFldCfg.Type)
}
} else { // Modify here when we add more supported cdr formats
fieldVal = "UNKNOWN"
return nil, fmt.Errorf("Unsupported CDR file format: %s", self.cdrType)
}
switch cfgFieldName {
switch cdrFldCfg.CdrFieldId {
case utils.TOR:
storedCdr.TOR = fieldVal
case utils.ACCID:
@@ -137,7 +154,7 @@ func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) {
return nil, fmt.Errorf("Cannot parse duration field with value: %s, err: %s", fieldVal, err.Error())
}
default: // Extra fields will not match predefined so they all show up here
storedCdr.ExtraFields[cfgFieldName] = fieldVal
storedCdr.ExtraFields[cdrFldCfg.CdrFieldId] = fieldVal
}
}

View File

@@ -48,6 +48,7 @@ README:
var cfgPath string
var cfg *config.CGRConfig
var cdrcCfg *config.CdrcConfig
var testLocal = flag.Bool("local", false, "Perform the tests only on local test environment, not by default.") // This flag will be passed here via "go test -local" args
var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here")
@@ -57,6 +58,9 @@ var waitRater = flag.Int("wait_rater", 300, "Number of miliseconds to wait for r
func init() {
cfgPath = path.Join(*dataDir, "conf", "samples", "apier_local_test.cfg")
cfg, _ = config.NewCGRConfigFromFile(&cfgPath)
if len(cfg.CdrcInstances) > 0 {
cdrcCfg = cfg.CdrcInstances[0]
}
}
var fileContent1 = `accid11,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
@@ -125,38 +129,41 @@ func TestCreateCdrFiles(t *testing.T) {
if !*testLocal {
return
}
if err := os.RemoveAll(cfg.CdrcCdrInDir); err != nil {
t.Fatal("Error removing folder: ", cfg.CdrcCdrInDir, err)
if cdrcCfg == nil {
t.Fatal("Empty default cdrc configuration")
}
if err := os.MkdirAll(cfg.CdrcCdrInDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cfg.CdrcCdrInDir, err)
if err := os.RemoveAll(cdrcCfg.CdrInDir); err != nil {
t.Fatal("Error removing folder: ", cdrcCfg.CdrInDir, err)
}
if err := os.RemoveAll(cfg.CdrcCdrOutDir); err != nil {
t.Fatal("Error removing folder: ", cfg.CdrcCdrInDir, err)
if err := os.MkdirAll(cdrcCfg.CdrInDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cdrcCfg.CdrInDir, err)
}
if err := os.MkdirAll(cfg.CdrcCdrOutDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cfg.CdrcCdrOutDir, err)
if err := os.RemoveAll(cdrcCfg.CdrOutDir); err != nil {
t.Fatal("Error removing folder: ", cdrcCfg.CdrOutDir, err)
}
if err := ioutil.WriteFile(path.Join(cfg.CdrcCdrInDir, "file1.csv"), []byte(fileContent1), 0644); err != nil {
if err := os.MkdirAll(cdrcCfg.CdrOutDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cdrcCfg.CdrOutDir, err)
}
if err := ioutil.WriteFile(path.Join(cdrcCfg.CdrInDir, "file1.csv"), []byte(fileContent1), 0644); err != nil {
t.Fatal(err.Error)
}
if err := ioutil.WriteFile(path.Join(cfg.CdrcCdrInDir, "file2.csv"), []byte(fileContent2), 0644); err != nil {
if err := ioutil.WriteFile(path.Join(cdrcCfg.CdrInDir, "file2.csv"), []byte(fileContent2), 0644); err != nil {
t.Fatal(err.Error)
}
}
func TestProcessCdrDir(t *testing.T) {
if !*testLocal {
return
}
if cfg.CdrcCdrs == utils.INTERNAL { // For now we only test over network
return
if cdrcCfg.CdrsAddress == utils.INTERNAL { // For now we only test over network
cdrcCfg.CdrsAddress = "127.0.0.1:2013"
}
if err := startEngine(); err != nil {
t.Fatal(err.Error())
}
cdrc, err := NewCdrc(cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, cfg.CdrcCsvSep,
cfg.CdrcCdrFields, nil)
cdrc, err := NewCdrc(cdrcCfg, true, nil)
if err != nil {
t.Fatal(err.Error())
}
@@ -171,13 +178,13 @@ func TestCreateCdr3File(t *testing.T) {
if !*testLocal {
return
}
if err := os.RemoveAll(cfg.CdrcCdrInDir); err != nil {
t.Fatal("Error removing folder: ", cfg.CdrcCdrInDir, err)
if err := os.RemoveAll(cdrcCfg.CdrInDir); err != nil {
t.Fatal("Error removing folder: ", cdrcCfg.CdrInDir, err)
}
if err := os.MkdirAll(cfg.CdrcCdrInDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cfg.CdrcCdrInDir, err)
if err := os.MkdirAll(cdrcCfg.CdrInDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cdrcCfg.CdrInDir, err)
}
if err := ioutil.WriteFile(path.Join(cfg.CdrcCdrInDir, "file3.csv"), []byte(fileContent3), 0644); err != nil {
if err := ioutil.WriteFile(path.Join(cdrcCfg.CdrInDir, "file3.csv"), []byte(fileContent3), 0644); err != nil {
t.Fatal(err.Error)
}
}
@@ -186,14 +193,13 @@ func TestProcessCdr3Dir(t *testing.T) {
if !*testLocal {
return
}
if cfg.CdrcCdrs == utils.INTERNAL { // For now we only test over network
return
if cdrcCfg.CdrsAddress == utils.INTERNAL { // For now we only test over network
cdrcCfg.CdrsAddress = "127.0.0.1:2013"
}
if err := startEngine(); err != nil {
t.Fatal(err.Error())
}
cdrc, err := NewCdrc(cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, ";",
cfg.CdrcCdrFields, nil)
cdrc, err := NewCdrc(cdrcCfg, true, nil)
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -19,27 +19,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package cdrc
import (
//"bytes"
//"encoding/csv"
//"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
//"io"
"reflect"
"testing"
"time"
"unicode/utf8"
)
func TestRecordForkCdr(t *testing.T) {
cgrConfig, _ := config.NewDefaultCGRConfig()
cgrConfig.CdrcCdrFields["supplier"] = []*utils.RSRField{&utils.RSRField{Id: "14"}}
csvSepRune, _ := utf8.DecodeRune([]byte(cgrConfig.CdrcCsvSep))
cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrType, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, csvSepRune,
cgrConfig.CdrcCdrFields, new(engine.CDRS), nil}
cdrcConfig := cgrConfig.CdrcInstances[0]
cdrcConfig.CdrFields = append(cdrcConfig.CdrFields, &config.CfgCdrField{Tag: "SupplierTest", Type: utils.CDRFIELD, CdrFieldId: "supplier", Value: []*utils.RSRField{&utils.RSRField{Id: "14"}}})
cdrc, err := NewCdrc(cdrcConfig, true, nil)
if err != nil {
t.Error(err)
}
cdrRow := []string{"firstField", "secondField"}
_, err := cdrc.recordToStoredCdr(cdrRow)
_, err = cdrc.recordToStoredCdr(cdrRow)
if err == nil {
t.Error("Failed to corectly detect missing fields from record")
}
@@ -54,7 +50,7 @@ func TestRecordForkCdr(t *testing.T) {
TOR: cdrRow[2],
AccId: cdrRow[3],
CdrHost: "0.0.0.0", // Got it over internal interface
CdrSource: cgrConfig.CdrcSourceId,
CdrSource: cdrcConfig.CdrSourceId,
ReqType: cdrRow[4],
Direction: cdrRow[5],
Tenant: cdrRow[6],

View File

@@ -28,19 +28,13 @@ import (
"io"
"os"
"strconv"
"strings"
"time"
)
const (
COST_DETAILS = "cost_details"
FILLER = "filler"
CONSTANT = "constant"
METATAG = "metatag"
CONCATENATED_CDRFIELD = "concatenated_cdrfield"
COMBIMED = "combimed"
DATETIME = "datetime"
HTTP_POST = "http_post"
META_EXPORTID = "export_id"
META_TIMENOW = "time_now"
META_FIRSTCDRATIME = "first_cdr_atime"
@@ -121,20 +115,25 @@ func (cdre *CdrExporter) getCdrCostDetails(cgrId, runId string) (string, error)
return string(ccJson), nil
}
func (cdre *CdrExporter) getCombimedCdrFieldVal(processedCdr *utils.StoredCdr, filterRule, fieldRule *utils.RSRField) (string, error) {
fltrPass, ftrPassValue := processedCdr.PassesFieldFilter(filterRule)
if !fltrPass {
return "", nil
}
for _, cdr := range cdre.cdrs {
if cdr.CgrId != processedCdr.CgrId {
continue // We only care about cdrs with same primary cdr behind
func (cdre *CdrExporter) getCombimedCdrFieldVal(processedCdr *utils.StoredCdr, cfgCdrFld *config.CfgCdrField) (string, error) {
var combinedVal string // Will result as combination of the field values, filters must match
for _, filterRule := range cfgCdrFld.Filter {
fltrPass, ftrPassValue := processedCdr.PassesFieldFilter(filterRule)
if !fltrPass {
return "", nil
}
if cdr.FieldAsString(&utils.RSRField{Id: filterRule.Id}) == ftrPassValue {
return cdr.FieldAsString(fieldRule), nil
for _, cdr := range cdre.cdrs {
if cdr.CgrId != processedCdr.CgrId {
continue // We only care about cdrs with same primary cdr behind
}
if cdr.FieldAsString(&utils.RSRField{Id: filterRule.Id}) == ftrPassValue { // First CDR with filte
for _, rsrRule := range cfgCdrFld.Value {
combinedVal += cdr.FieldAsString(rsrRule)
}
}
}
}
return "", nil
return combinedVal, nil
}
// Check if the destination should be masked in output
@@ -145,17 +144,20 @@ func (cdre *CdrExporter) maskedDestination(destination string) bool {
return false
}
func (cdre *CdrExporter) getDateTimeFieldVal(cdr *utils.StoredCdr, fltrRl, fieldRl *utils.RSRField, layout string) (string, error) {
if fieldRl == nil {
func (cdre *CdrExporter) getDateTimeFieldVal(cdr *utils.StoredCdr, cfgCdrFld *config.CfgCdrField) (string, error) {
if len(cfgCdrFld.Value) == 0 {
return "", nil
}
if fltrPass, _ := cdr.PassesFieldFilter(fltrRl); !fltrPass {
return "", fmt.Errorf("Field: %s not matching filter rule %v", fltrRl.Id, fltrRl)
for _, fltrRl := range cfgCdrFld.Filter {
if fltrPass, _ := cdr.PassesFieldFilter(fltrRl); !fltrPass {
return "", fmt.Errorf("Field: %s not matching filter rule %v", fltrRl.Id, fltrRl)
}
}
layout := cfgCdrFld.Layout
if len(layout) == 0 {
layout = time.RFC3339
}
if dtFld, err := utils.ParseTimeDetectLayout(cdr.FieldAsString(fieldRl)); err != nil {
if dtFld, err := utils.ParseTimeDetectLayout(cdr.FieldAsString(cfgCdrFld.Value[0])); err != nil { // Only one rule makes sense here
return "", err
} else {
return dtFld.Format(layout), nil
@@ -163,39 +165,43 @@ func (cdre *CdrExporter) getDateTimeFieldVal(cdr *utils.StoredCdr, fltrRl, field
}
// Extracts the value specified by cfgHdr out of cdr
func (cdre *CdrExporter) cdrFieldValue(cdr *utils.StoredCdr, fltrRl, rsrFld *utils.RSRField, layout string) (string, error) {
if rsrFld == nil {
return "", nil
}
if fltrPass, _ := cdr.PassesFieldFilter(fltrRl); !fltrPass {
return "", fmt.Errorf("Field: %s not matching filter rule %v", fltrRl.Id, fltrRl)
func (cdre *CdrExporter) cdrFieldValue(cdr *utils.StoredCdr, cfgCdrFld *config.CfgCdrField) (string, error) {
for _, fltrRl := range cfgCdrFld.Filter {
if fltrPass, _ := cdr.PassesFieldFilter(fltrRl); !fltrPass {
return "", fmt.Errorf("Field: %s not matching filter rule %v", fltrRl.Id, fltrRl)
}
}
layout := cfgCdrFld.Layout
if len(layout) == 0 {
layout = time.RFC3339
}
var cdrVal string
switch rsrFld.Id {
case COST_DETAILS: // Special case when we need to further extract cost_details out of logDb
if cdrVal, err = cdre.getCdrCostDetails(cdr.CgrId, cdr.MediationRunId); err != nil {
return "", err
var retVal string // Concatenate the resulting values
for _, rsrFld := range cfgCdrFld.Value {
var cdrVal string
switch rsrFld.Id {
case COST_DETAILS: // Special case when we need to further extract cost_details out of logDb
if cdrVal, err = cdre.getCdrCostDetails(cdr.CgrId, cdr.MediationRunId); err != nil {
return "", err
}
case utils.COST:
cdrVal = cdr.FormatCost(cdre.costShiftDigits, cdre.roundDecimals)
case utils.USAGE:
cdrVal = cdr.FormatUsage(layout)
case utils.SETUP_TIME:
cdrVal = cdr.SetupTime.Format(layout)
case utils.ANSWER_TIME: // Format time based on layout
cdrVal = cdr.AnswerTime.Format(layout)
case utils.DESTINATION:
cdrVal = cdr.FieldAsString(rsrFld)
if cdre.maskLen != -1 && cdre.maskedDestination(cdrVal) {
cdrVal = MaskDestination(cdrVal, cdre.maskLen)
}
default:
cdrVal = cdr.FieldAsString(rsrFld)
}
case utils.COST:
cdrVal = cdr.FormatCost(cdre.costShiftDigits, cdre.roundDecimals)
case utils.USAGE:
cdrVal = cdr.FormatUsage(layout)
case utils.SETUP_TIME:
cdrVal = cdr.SetupTime.Format(layout)
case utils.ANSWER_TIME: // Format time based on layout
cdrVal = cdr.AnswerTime.Format(layout)
case utils.DESTINATION:
cdrVal = cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION})
if cdre.maskLen != -1 && cdre.maskedDestination(cdrVal) {
cdrVal = MaskDestination(cdrVal, cdre.maskLen)
}
default:
cdrVal = cdr.FieldAsString(rsrFld)
retVal += cdrVal
}
return rsrFld.ParseValue(cdrVal), nil
return retVal, nil
}
// Handle various meta functions used in header/trailer
@@ -212,15 +218,12 @@ func (cdre *CdrExporter) metaHandler(tag, arg string) (string, error) {
case META_NRCDRS:
return strconv.Itoa(cdre.numberOfRecords), nil
case META_DURCDRS:
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
emulatedCdr := &utils.StoredCdr{TOR: utils.VOICE, Usage: cdre.totalDuration}
return emulatedCdr.FormatUsage(arg), nil
case META_SMSUSAGE:
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
emulatedCdr := &utils.StoredCdr{TOR: utils.SMS, Usage: cdre.totalSmsUsage}
return emulatedCdr.FormatUsage(arg), nil
case META_DATAUSAGE:
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
emulatedCdr := &utils.StoredCdr{TOR: utils.DATA, Usage: cdre.totalDataUsage}
return emulatedCdr.FormatUsage(arg), nil
case META_COSTCDRS:
@@ -241,23 +244,27 @@ func (cdre *CdrExporter) composeHeader() error {
for _, cfgFld := range cdre.exportTemplate.HeaderFields {
var outVal string
switch cfgFld.Type {
case FILLER:
outVal = cfgFld.Value
case utils.FILLER:
for _, rsrFld := range cfgFld.Value {
outVal += rsrFld.ParseValue("")
}
cfgFld.Padding = "right"
case CONSTANT:
outVal = cfgFld.Value
case METATAG:
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
case utils.CONSTANT:
for _, rsrFld := range cfgFld.Value {
outVal += rsrFld.ParseValue("")
}
case utils.METATAG:
outVal, err = cdre.metaHandler(cfgFld.CdrFieldId, cfgFld.Layout)
default:
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
}
if err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Name, err.Error()))
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Tag, err.Error()))
return err
}
fmtOut := outVal
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Name, err.Error()))
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Tag, err.Error()))
return err
}
cdre.header = append(cdre.header, fmtOut)
@@ -270,23 +277,27 @@ func (cdre *CdrExporter) composeTrailer() error {
for _, cfgFld := range cdre.exportTemplate.TrailerFields {
var outVal string
switch cfgFld.Type {
case FILLER:
outVal = cfgFld.Value
case utils.FILLER:
for _, rsrFld := range cfgFld.Value {
outVal += rsrFld.ParseValue("")
}
cfgFld.Padding = "right"
case CONSTANT:
outVal = cfgFld.Value
case METATAG:
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
case utils.CONSTANT:
for _, rsrFld := range cfgFld.Value {
outVal += rsrFld.ParseValue("")
}
case utils.METATAG:
outVal, err = cdre.metaHandler(cfgFld.CdrFieldId, cfgFld.Layout)
default:
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
}
if err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Name, err.Error()))
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Tag, err.Error()))
return err
}
fmtOut := outVal
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Name, err.Error()))
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Tag, err.Error()))
return err
}
cdre.trailer = append(cdre.trailer, fmtOut)
@@ -310,38 +321,38 @@ func (cdre *CdrExporter) processCdr(cdr *utils.StoredCdr) error {
for idx, cfgFld := range cdre.exportTemplate.ContentFields {
var outVal string
switch cfgFld.Type {
case FILLER:
outVal = cfgFld.Value
case utils.FILLER:
for _, rsrFld := range cfgFld.Value {
outVal += rsrFld.ParseValue("")
}
cfgFld.Padding = "right"
case CONSTANT:
outVal = cfgFld.Value
case utils.CONSTANT:
for _, rsrFld := range cfgFld.Value {
outVal += rsrFld.ParseValue("")
}
case utils.CDRFIELD:
outVal, err = cdre.cdrFieldValue(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField(), cfgFld.Layout)
outVal, err = cdre.cdrFieldValue(cdr, cfgFld)
case DATETIME:
outVal, err = cdre.getDateTimeFieldVal(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField(), cfgFld.Layout)
case HTTP_POST:
outVal, err = cdre.getDateTimeFieldVal(cdr, cfgFld)
case utils.HTTP_POST:
var outValByte []byte
if outValByte, err = utils.HttpJsonPost(cfgFld.Value, cdre.httpSkipTlsCheck, cdr); err == nil {
var httpAddr string
for _, rsrFld := range cfgFld.Value {
httpAddr += rsrFld.ParseValue("")
}
if outValByte, err = utils.HttpJsonPost(httpAddr, cdre.httpSkipTlsCheck, cdr); err == nil {
outVal = string(outValByte)
if len(outVal) == 0 && cfgFld.Mandatory {
err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Name)
err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Tag)
}
}
case COMBIMED:
outVal, err = cdre.getCombimedCdrFieldVal(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField())
case CONCATENATED_CDRFIELD:
for _, fld := range strings.Split(cfgFld.Value, ",") {
if fldOut, err := cdre.cdrFieldValue(cdr, cfgFld.Filter, &utils.RSRField{Id: fld}, cfgFld.Layout); err != nil {
break // The error will be reported bellow
} else {
outVal += fldOut
}
}
case METATAG:
if cfgFld.Value == META_MASKDESTINATION {
outVal, err = cdre.metaHandler(cfgFld.Value, cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION}))
case utils.COMBIMED:
outVal, err = cdre.getCombimedCdrFieldVal(cdr, cfgFld)
case utils.METATAG:
if cfgFld.CdrFieldId == META_MASKDESTINATION {
outVal, err = cdre.metaHandler(cfgFld.CdrFieldId, cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION}))
} else {
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
outVal, err = cdre.metaHandler(cfgFld.CdrFieldId, cfgFld.Layout)
}
}
if err != nil {
@@ -350,7 +361,7 @@ func (cdre *CdrExporter) processCdr(cdr *utils.StoredCdr) error {
}
fmtOut := outVal
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s, runid: %s, fieldName: %s, fieldValue: %s, error: %s", cdr.CgrId, cdr.MediationRunId, cfgFld.Name, outVal, err.Error()))
engine.Logger.Err(fmt.Sprintf("<CdreFw> 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
}
cdrRow[idx] += fmtOut

View File

@@ -51,20 +51,23 @@ func TestCdreGetCombimedCdrFieldVal(t *testing.T) {
Category: "call", Account: "1000", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: "RETAIL1", Cost: 5.01},
}
cdre, err := NewCdrExporter(cdrs, logDb, cfg.CdreDefaultInstance, cfg.CdreDefaultInstance.CdrFormat, cfg.CdreDefaultInstance.FieldSeparator,
"firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify)
if err != nil {
t.Error("Unexpected error received: ", err)
}
fltrRule, _ := utils.NewRSRField("~mediation_runid:s/default/RUN_RTL/")
if costVal, err := cdre.getCombimedCdrFieldVal(cdrs[3], fltrRule, &utils.RSRField{Id: "cost"}); err != nil {
fltrRule, _ := utils.ParseRSRFields("~mediation_runid:s/default/RUN_RTL/", utils.INFIELD_SEP)
val, _ := utils.ParseRSRFields("cost", utils.INFIELD_SEP)
cfgCdrFld, _ := config.NewCfgCdrFieldWithDefaults(false, val, fltrRule, nil, nil, nil, nil, nil, nil, nil, nil)
if costVal, err := cdre.getCombimedCdrFieldVal(cdrs[3], cfgCdrFld); err != nil {
t.Error(err)
} else if costVal != "1.01" {
t.Error("Expecting: 1.01, received: ", costVal)
}
fltrRule, _ = utils.NewRSRField("~mediation_runid:s/default/RETAIL1/")
if acntVal, err := cdre.getCombimedCdrFieldVal(cdrs[3], fltrRule, &utils.RSRField{Id: "account"}); err != nil {
fltrRule, _ = utils.ParseRSRFields("~mediation_runid:s/default/RETAIL1/", utils.INFIELD_SEP)
val, _ = utils.ParseRSRFields("account", utils.INFIELD_SEP)
cfgCdrFld, _ = config.NewCfgCdrFieldWithDefaults(false, val, fltrRule, nil, nil, nil, nil, nil, nil, nil, nil)
if acntVal, err := cdre.getCombimedCdrFieldVal(cdrs[3], cfgCdrFld); err != nil {
t.Error(err)
} else if acntVal != "1000" {
t.Error("Expecting: 1000, received: ", acntVal)
@@ -78,18 +81,24 @@ func TestGetDateTimeFieldVal(t *testing.T) {
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.01,
ExtraFields: map[string]string{"stop_time": "2014-06-11 19:19:00 +0000 UTC", "fieldextr2": "valextr2"}}
if cdrVal, err := cdreTst.getDateTimeFieldVal(cdrTst, nil, &utils.RSRField{Id: "stop_time"}, "2006-01-02 15:04:05"); err != nil {
val, _ := utils.ParseRSRFields("stop_time", utils.INFIELD_SEP)
layout := "2006-01-02 15:04:05"
cfgCdrFld, _ := config.NewCfgCdrFieldWithDefaults(false, val, nil, nil, nil, nil, nil, &layout, nil, nil, nil)
if cdrVal, err := cdreTst.getDateTimeFieldVal(cdrTst, cfgCdrFld); err != nil {
t.Error(err)
} else if cdrVal != "2014-06-11 19:19:00" {
t.Error("Expecting: 2014-06-11 19:19:00, got: ", cdrVal)
}
// Test filter
fltrRule, _ := utils.NewRSRField("~tenant:s/(.+)/itsyscom.com/")
if _, err := cdreTst.getDateTimeFieldVal(cdrTst, fltrRule, &utils.RSRField{Id: "stop_time"}, "2006-01-02 15:04:05"); err == nil {
fltr, _ := utils.ParseRSRFields("~tenant:s/(.+)/itsyscom.com/", utils.INFIELD_SEP)
cfgCdrFld, _ = config.NewCfgCdrFieldWithDefaults(false, val, fltr, nil, nil, nil, nil, &layout, nil, nil, nil)
if _, err := cdreTst.getDateTimeFieldVal(cdrTst, cfgCdrFld); err == nil {
t.Error(err)
}
val, _ = utils.ParseRSRFields("fieldextr2", utils.INFIELD_SEP)
cfgCdrFld, _ = config.NewCfgCdrFieldWithDefaults(false, val, nil, nil, nil, nil, nil, &layout, nil, nil, nil)
// Test time parse error
if _, err := cdreTst.getDateTimeFieldVal(cdrTst, nil, &utils.RSRField{Id: "fieldextr2"}, "2006-01-02 15:04:05"); err == nil {
if _, err := cdreTst.getDateTimeFieldVal(cdrTst, cfgCdrFld); err == nil {
t.Error("Should give error here, got none.")
}
}
@@ -100,14 +109,16 @@ func TestCdreCdrFieldValue(t *testing.T) {
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.01}
fltrRule, _ := utils.NewRSRField("~tenant:s/(.+)/cgrates.org/")
if val, err := cdre.cdrFieldValue(cdr, fltrRule, &utils.RSRField{Id: "destination"}, ""); err != nil {
val, _ := utils.ParseRSRFields("destination", utils.INFIELD_SEP)
cfgCdrFld, _ := config.NewCfgCdrFieldWithDefaults(false, val, nil, nil, nil, nil, nil, nil, nil, nil, nil)
if val, err := cdre.cdrFieldValue(cdr, cfgCdrFld); err != nil {
t.Error(err)
} else if val != cdr.Destination {
t.Errorf("Expecting: %s, received: %s", cdr.Destination, val)
}
fltrRule, _ = utils.NewRSRField("~tenant:s/(.+)/itsyscom.com/")
if _, err := cdre.cdrFieldValue(cdr, fltrRule, &utils.RSRField{Id: "destination"}, ""); err == nil {
fltr, _ := utils.ParseRSRFields("~tenant:s/(.+)/itsyscom.com/", utils.INFIELD_SEP)
cfgCdrFld, _ = config.NewCfgCdrFieldWithDefaults(false, val, fltr, nil, nil, nil, nil, nil, nil, nil, nil)
if _, err := cdre.cdrFieldValue(cdr, cfgCdrFld); err == nil {
t.Error("Failed to use filter")
}
}

View File

@@ -28,50 +28,52 @@ import (
"time"
)
var hdrCfgFlds = []*config.CgrXmlCfgCdrField{
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "10", Width: 2},
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3},
&config.CgrXmlCfgCdrField{Name: "DistributorCode", Type: CONSTANT, Value: "VOI", Width: 3},
&config.CgrXmlCfgCdrField{Name: "FileSeqNr", Type: METATAG, Value: "export_id", Width: 5, Strip: "right", Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "LastCdr", Type: METATAG, Value: META_LASTCDRATIME, Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "FileCreationfTime", Type: METATAG, Value: "time_now", Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "FileVersion", Type: CONSTANT, Value: "01", Width: 2},
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 105},
var hdrCfgFlds = []*config.XmlCfgCdrField{
&config.XmlCfgCdrField{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("10"), Width: utils.IntPointer(2)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Filler1"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(3)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("DistributorCode"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("VOI"), Width: utils.IntPointer(3)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("FileSeqNr"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer("export_id"), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("zeroleft")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("LastCdr"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_LASTCDRATIME), Width: utils.IntPointer(12), Layout: utils.StringPointer("020106150400")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("FileCreationfTime"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer("time_now"), Width: utils.IntPointer(12), Layout: utils.StringPointer("020106150400")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("FileVersion"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("01"), Width: utils.IntPointer(2)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Filler2"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(105)},
}
var contentCfgFlds = []*config.CgrXmlCfgCdrField{
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "20", Width: 2},
&config.CgrXmlCfgCdrField{Name: "Account", Type: utils.CDRFIELD, Value: utils.ACCOUNT, Width: 12, Strip: "left", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Subject", Type: utils.CDRFIELD, Value: utils.SUBJECT, Width: 5, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "CLI", Type: utils.CDRFIELD, Value: "cli", Width: 15, Strip: "xright", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Destination", Type: utils.CDRFIELD, Value: utils.DESTINATION, Width: 24, Strip: "xright", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "TOR", Type: CONSTANT, Value: "02", Width: 2},
&config.CgrXmlCfgCdrField{Name: "SubtypeTOR", Type: CONSTANT, Value: "11", Width: 4, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "SetupTime", Type: utils.CDRFIELD, Value: utils.SETUP_TIME, Width: 12, Strip: "right", Padding: "right", Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "Duration", Type: utils.CDRFIELD, Value: utils.USAGE, Width: 6, Strip: "right", Padding: "right", Layout: utils.SECONDS},
&config.CgrXmlCfgCdrField{Name: "DataVolume", Type: FILLER, Width: 6},
&config.CgrXmlCfgCdrField{Name: "TaxCode", Type: CONSTANT, Value: "1", Width: 1},
&config.CgrXmlCfgCdrField{Name: "OperatorCode", Type: utils.CDRFIELD, Value: "opercode", Width: 2, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "ProductId", Type: utils.CDRFIELD, Value: "productid", Width: 5, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "NetworkId", Type: CONSTANT, Value: "3", Width: 1},
&config.CgrXmlCfgCdrField{Name: "CallId", Type: utils.CDRFIELD, Value: utils.ACCID, Width: 16, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8},
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8},
&config.CgrXmlCfgCdrField{Name: "TerminationCode", Type: CONCATENATED_CDRFIELD, Value: "operator,product", Width: 5, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Cost", Type: utils.CDRFIELD, Value: utils.COST, Width: 9, Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "DestinationPrivacy", Type: METATAG, Value: META_MASKDESTINATION, Width: 1},
var contentCfgFlds = []*config.XmlCfgCdrField{
&config.XmlCfgCdrField{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("20"), Width: utils.IntPointer(2)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Account"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.ACCOUNT), Width: utils.IntPointer(12), Strip: utils.StringPointer("left"), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Subject"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.SUBJECT), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("CLI"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer("cli"), Width: utils.IntPointer(15), Strip: utils.StringPointer("xright"), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Destination"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.DESTINATION), Width: utils.IntPointer(24), Strip: utils.StringPointer("xright"), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("TOR"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("02"), Width: utils.IntPointer(2)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("SubtypeTOR"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("11"), Width: utils.IntPointer(4), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("SetupTime"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.SETUP_TIME), Width: utils.IntPointer(12), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right"),
Layout: utils.StringPointer("020106150400")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Duration"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.USAGE), Width: utils.IntPointer(6), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right"),
Layout: utils.StringPointer(utils.SECONDS)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("DataVolume"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(6)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("TaxCode"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("1"), Width: utils.IntPointer(1)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("OperatorCode"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer("opercode"), Width: utils.IntPointer(2), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("ProductId"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer("productid"), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("NetworkId"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("3"), Width: utils.IntPointer(1)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("CallId"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.ACCID), Width: utils.IntPointer(16), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Filler"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(8)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Filler"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(8)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("TerminationCode"), Type: utils.StringPointer(CONCATENATED_CDRFIELD), Value: utils.StringPointer("operator,product"), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("right")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Cost"), Type: utils.StringPointer(utils.CDRFIELD), Value: utils.StringPointer(utils.COST), Width: utils.IntPointer(9), Padding: utils.StringPointer("zeroleft")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("DestinationPrivacy"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_MASKDESTINATION), Width: utils.IntPointer(1)},
}
var trailerCfgFlds = []*config.CgrXmlCfgCdrField{
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "90", Width: 2},
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3},
&config.CgrXmlCfgCdrField{Name: "DistributorCode", Type: CONSTANT, Value: "VOI", Width: 3},
&config.CgrXmlCfgCdrField{Name: "FileSeqNr", Type: METATAG, Value: META_EXPORTID, Width: 5, Strip: "right", Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "NumberOfRecords", Type: METATAG, Value: META_NRCDRS, Width: 6, Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "CdrsDuration", Type: METATAG, Value: META_DURCDRS, Width: 8, Padding: "zeroleft", Layout: "seconds"},
&config.CgrXmlCfgCdrField{Name: "FirstCdrTime", Type: METATAG, Value: META_FIRSTCDRATIME, Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "LastCdrTime", Type: METATAG, Value: META_LASTCDRATIME, Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 93},
var trailerCfgFlds = []*config.XmlCfgCdrField{
&config.XmlCfgCdrField{Tag: utils.StringPointer("TypeOfRecord"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("90"), Width: utils.IntPointer(2)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Filler1"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(3)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("DistributorCode"), Type: utils.StringPointer(utils.CONSTANT), Value: utils.StringPointer("VOI"), Width: utils.IntPointer(3)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("FileSeqNr"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_EXPORTID), Width: utils.IntPointer(5), Strip: utils.StringPointer("right"), Padding: utils.StringPointer("zeroleft")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("NumberOfRecords"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_NRCDRS), Width: utils.IntPointer(6), Padding: utils.StringPointer("zeroleft")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("CdrsDuration"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_DURCDRS), Width: utils.IntPointer(8), Padding: utils.StringPointer("zeroleft"), Layout: utils.StringPointer(utils.SECONDS)},
&config.XmlCfgCdrField{Tag: utils.StringPointer("FirstCdrTime"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_FIRSTCDRATIME), Width: utils.IntPointer(12), Layout: utils.StringPointer("020106150400")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("LastCdrTime"), Type: utils.StringPointer(utils.METATAG), Value: utils.StringPointer(META_LASTCDRATIME), Width: utils.IntPointer(12), Layout: utils.StringPointer("020106150400")},
&config.XmlCfgCdrField{Tag: utils.StringPointer("Filler2"), Type: utils.StringPointer(utils.FILLER), Width: utils.IntPointer(93)},
}
// Write one CDR and test it's results only for content buffer
@@ -95,7 +97,11 @@ func TestWriteCdr(t *testing.T) {
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr}, logDb, exportTpl.AsCdreConfig(), utils.CDRE_FIXED_WIDTH, ',', "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify)
cdreCfg, err := config.NewCdreConfigFromXmlCdreCfg(exportTpl)
if err != nil {
t.Error(err)
}
cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr}, logDb, cdreCfg, utils.CDRE_FIXED_WIDTH, ',', "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify)
if err != nil {
t.Error(err)
}
@@ -171,7 +177,11 @@ func TestWriteCdrs(t *testing.T) {
ExtraFields: map[string]string{"productnumber": "12344", "fieldextr2": "valextr2"},
}
cfg, _ := config.NewDefaultCGRConfig()
cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr1, cdr2, cdr3, cdr4}, logDb, exportTpl.AsCdreConfig(), utils.CDRE_FIXED_WIDTH, ',',
cdreCfg, err := config.NewCdreConfigFromXmlCdreCfg(exportTpl)
if err != nil {
t.Error(err)
}
cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr1, cdr2, cdr3, cdr4}, logDb, cdreCfg, utils.CDRE_FIXED_WIDTH, ',',
"fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify)
if err != nil {
t.Error(err)

View File

@@ -59,7 +59,6 @@ var (
raterEnabled = flag.Bool("rater", false, "Enforce starting of the rater daemon overwriting config")
schedEnabled = flag.Bool("scheduler", false, "Enforce starting of the scheduler daemon .overwriting config")
cdrsEnabled = flag.Bool("cdrs", false, "Enforce starting of the cdrs daemon overwriting config")
cdrcEnabled = flag.Bool("cdrc", false, "Enforce starting of the cdrc service overwriting config")
mediatorEnabled = flag.Bool("mediator", false, "Enforce starting of the mediator service overwriting config")
pidFile = flag.String("pid", "", "Write pid file")
bal = balancer2go.NewBalancer()
@@ -125,11 +124,11 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD
}
// Fires up a cdrc instance
func startCdrc(cdrsChan chan struct{}, cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField) {
if cdrsAddress == utils.INTERNAL {
func startCdrc(cdrsChan chan struct{}, cdrcCfg *config.CdrcConfig, httpSkipTlsCheck bool, cdrServer *engine.CDRS) {
if cdrcCfg.CdrsAddress == utils.INTERNAL {
<-cdrsChan // Wait for CDRServer to come up before start processing
}
cdrc, err := cdrc.NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId, runDelay, csvSep, cdrFields, cdrServer)
cdrc, err := cdrc.NewCdrc(cdrcCfg, httpSkipTlsCheck, cdrServer)
if err != nil {
engine.Logger.Crit(fmt.Sprintf("Cdrc config parsing error: %s", err.Error()))
exitChan <- true
@@ -329,9 +328,6 @@ func main() {
if *cdrsEnabled {
cfg.CDRSEnabled = *cdrsEnabled
}
if *cdrcEnabled {
cfg.CdrcEnabled = *cdrcEnabled
}
if *mediatorEnabled {
cfg.MediatorEnabled = *mediatorEnabled
}
@@ -491,19 +487,13 @@ func main() {
go shutdownSessionmanagerSingnalHandler()
}
var cdrcEnabled bool
if cfg.CdrcEnabled { // Start default cdrc configured in csv here
cdrcEnabled = true
go startCdrc(cdrsChan, cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, cfg.CdrcCsvSep, cfg.CdrcCdrFields)
}
if cfg.XmlCfgDocument != nil {
for _, xmlCdrc := range cfg.XmlCfgDocument.GetCdrcCfgs("") {
if !xmlCdrc.Enabled {
continue
}
cdrcEnabled = true
go startCdrc(cdrsChan, xmlCdrc.CdrsAddress, xmlCdrc.CdrType, xmlCdrc.CdrInDir, xmlCdrc.CdrOutDir,
xmlCdrc.CdrSourceId, time.Duration(xmlCdrc.RunDelay), xmlCdrc.CsvSeparator, xmlCdrc.CdrRSRFields())
for _, cdrcConfig := range cfg.CdrcInstances {
if cdrcConfig.Enabled == false {
continue // Ignore not enabled
} else if !cdrcEnabled {
cdrcEnabled = true // Mark that at least one cdrc service is active
}
go startCdrc(cdrsChan, cdrcConfig, cfg.HttpSkipTlsVerify, cdrServer)
}
if cdrcEnabled {
engine.Logger.Info("Starting CGRateS CDR client.")

View File

@@ -19,32 +19,72 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package config
import (
"errors"
"github.com/cgrates/cgrates/utils"
)
// Converts a list of field identifiers into proper CDR field content
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.setDefaultFieldProperties(withFixedWith); err != nil { // Set default fixed width properties to be used later if needed
return nil, err
}
cdrFields[idx] = cdrFld
}
}
return cdrFields, nil
func NewDefaultCdreConfig() *CdreConfig {
cdreCfg := new(CdreConfig)
cdreCfg.setDefaults()
return cdreCfg
}
func NewDefaultCdreConfig() (*CdreConfig, error) {
cdreCfg := new(CdreConfig)
if err := cdreCfg.setDefaults(); err != nil {
return nil, err
func NewCdreConfigFromXmlCdreCfg(xmlCdreCfg *CgrXmlCdreCfg) (*CdreConfig, error) {
var err error
cdreCfg := NewDefaultCdreConfig()
if xmlCdreCfg.CdrFormat != nil {
cdreCfg.CdrFormat = *xmlCdreCfg.CdrFormat
}
if xmlCdreCfg.FieldSeparator != nil && len(*xmlCdreCfg.FieldSeparator) == 1 {
sepStr := *xmlCdreCfg.FieldSeparator
cdreCfg.FieldSeparator = rune(sepStr[0])
}
if xmlCdreCfg.DataUsageMultiplyFactor != nil {
cdreCfg.DataUsageMultiplyFactor = *xmlCdreCfg.DataUsageMultiplyFactor
}
if xmlCdreCfg.CostMultiplyFactor != nil {
cdreCfg.CostMultiplyFactor = *xmlCdreCfg.CostMultiplyFactor
}
if xmlCdreCfg.CostRoundingDecimals != nil {
cdreCfg.CostRoundingDecimals = *xmlCdreCfg.CostRoundingDecimals
}
if xmlCdreCfg.CostShiftDigits != nil {
cdreCfg.CostShiftDigits = *xmlCdreCfg.CostShiftDigits
}
if xmlCdreCfg.MaskDestId != nil {
cdreCfg.MaskDestId = *xmlCdreCfg.MaskDestId
}
if xmlCdreCfg.MaskLength != nil {
cdreCfg.MaskLength = *xmlCdreCfg.MaskLength
}
if xmlCdreCfg.ExportDir != nil {
cdreCfg.ExportDir = *xmlCdreCfg.ExportDir
}
if xmlCdreCfg.Header != nil {
cdreCfg.HeaderFields = make([]*CfgCdrField, len(xmlCdreCfg.Header.Fields))
for idx, xmlFld := range xmlCdreCfg.Header.Fields {
cdreCfg.HeaderFields[idx], err = NewCfgCdrFieldFromCgrXmlCfgCdrField(xmlFld, cdreCfg.CdrFormat == utils.CDRE_FIXED_WIDTH)
if err != nil {
return nil, err
}
}
}
if xmlCdreCfg.Content != nil {
cdreCfg.ContentFields = make([]*CfgCdrField, len(xmlCdreCfg.Content.Fields))
for idx, xmlFld := range xmlCdreCfg.Content.Fields {
cdreCfg.ContentFields[idx], err = NewCfgCdrFieldFromCgrXmlCfgCdrField(xmlFld, cdreCfg.CdrFormat == utils.CDRE_FIXED_WIDTH)
if err != nil {
return nil, err
}
}
}
if xmlCdreCfg.Trailer != nil {
cdreCfg.TrailerFields = make([]*CfgCdrField, len(xmlCdreCfg.Trailer.Fields))
for idx, xmlFld := range xmlCdreCfg.Trailer.Fields {
cdreCfg.TrailerFields[idx], err = NewCfgCdrFieldFromCgrXmlCfgCdrField(xmlFld, cdreCfg.CdrFormat == utils.CDRE_FIXED_WIDTH)
if err != nil {
return nil, err
}
}
}
return cdreCfg, nil
}
@@ -60,9 +100,9 @@ type CdreConfig struct {
MaskDestId string
MaskLength int
ExportDir string
HeaderFields []*CdreCdrField
ContentFields []*CdreCdrField
TrailerFields []*CdreCdrField
HeaderFields []*CfgCdrField
ContentFields []*CfgCdrField
TrailerFields []*CfgCdrField
}
// Set here defaults
@@ -76,7 +116,7 @@ func (cdreCfg *CdreConfig) setDefaults() error {
cdreCfg.MaskDestId = ""
cdreCfg.MaskLength = 0
cdreCfg.ExportDir = "/var/log/cgrates/cdre"
if flds, err := NewCdreCdrFieldsFromIds(false, utils.CGRID, utils.MEDI_RUNID, utils.TOR, utils.ACCID, utils.REQTYPE, utils.DIRECTION, utils.TENANT,
if flds, err := NewCfgCdrFieldsFromIds(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 {
@@ -84,161 +124,3 @@ func (cdreCfg *CdreConfig) setDefaults() error {
}
return nil
}
type CdreCdrField struct {
Name string
Type string
Value string
Width int
Strip string
Padding string
Layout string
Filter *utils.RSRField
Mandatory bool
valueAsRsrField *utils.RSRField // Cached if the need arrises
}
func (cdrField *CdreCdrField) ValueAsRSRField() *utils.RSRField {
return cdrField.valueAsRsrField
}
// Should be called on .fwv configuration without providing default values for fixed with parameters
func (cdrField *CdreCdrField) setDefaultFieldProperties(fixedWidth bool) error {
if cdrField.valueAsRsrField == nil {
return errors.New("Missing valueAsRsrField")
}
switch cdrField.valueAsRsrField.Id {
case utils.CGRID:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 40
}
case utils.ORDERID:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 11
cdrField.Padding = "left"
}
case utils.TOR:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 6
cdrField.Padding = "left"
}
case utils.ACCID:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 36
cdrField.Strip = "left"
cdrField.Padding = "left"
}
case utils.CDRHOST:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 15
cdrField.Strip = "left"
cdrField.Padding = "left"
}
case utils.CDRSOURCE:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 15
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.REQTYPE:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 13
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.DIRECTION:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 4
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.TENANT:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.CATEGORY:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 10
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.ACCOUNT:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.SUBJECT:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.DESTINATION:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.SETUP_TIME:
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.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.Mandatory = true
if fixedWidth {
cdrField.Width = 30
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.MEDI_RUNID:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 20
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.COST:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
default:
cdrField.Mandatory = false
if fixedWidth {
cdrField.Width = 30
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
}
return nil
}

View File

@@ -19,58 +19,42 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package config
import (
"fmt"
"github.com/cgrates/cgrates/utils"
"reflect"
"testing"
)
func TestCdreCfgNewCdreCdrFieldsFromIds(t *testing.T) {
expectedFlds := []*CdreCdrField{
&CdreCdrField{
Name: utils.CGRID,
Type: utils.CDRFIELD,
Value: utils.CGRID,
Width: 40,
Strip: "",
Padding: "",
Layout: "",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
func TestNewCfgCdrFieldsFromIds(t *testing.T) {
expectedFlds := []*CfgCdrField{
&CfgCdrField{
Tag: utils.CGRID,
Type: utils.CDRFIELD,
CdrFieldId: utils.CGRID,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.CGRID}},
Width: 40,
Mandatory: true,
},
&CdreCdrField{
Name: "extra1",
Type: utils.CDRFIELD,
Value: "extra1",
Width: 30,
Strip: "xright",
Padding: "left",
Layout: "",
Mandatory: false,
valueAsRsrField: &utils.RSRField{Id: "extra1"},
&CfgCdrField{
Tag: "extra1",
Type: utils.CDRFIELD,
CdrFieldId: "extra1",
Value: []*utils.RSRField{
&utils.RSRField{Id: "extra1"}},
Width: 30,
Strip: "xright",
Padding: "left",
Mandatory: false,
},
}
if cdreFlds, err := NewCdreCdrFieldsFromIds(true, utils.CGRID, "extra1"); err != nil {
if cdreFlds, err := NewCfgCdrFieldsFromIds(true, utils.CGRID, "extra1"); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedFlds, cdreFlds) {
t.Errorf("Expected: %v, received: %v", expectedFlds, cdreFlds)
}
}
func TestCdreCfgValueAsRSRField(t *testing.T) {
cdreCdrFld := &CdreCdrField{
Name: utils.CGRID,
Type: utils.CDRFIELD,
Value: utils.CGRID,
Width: 10,
Strip: "xright",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
}
if rsrVal := cdreCdrFld.ValueAsRSRField(); rsrVal != cdreCdrFld.valueAsRsrField {
t.Error("Unexpected value received: ", rsrVal)
}
}
func TestCdreCfgNewDefaultCdreConfig(t *testing.T) {
eCdreCfg := new(CdreConfig)
eCdreCfg.CdrFormat = utils.CSV
@@ -82,402 +66,133 @@ func TestCdreCfgNewDefaultCdreConfig(t *testing.T) {
eCdreCfg.MaskDestId = ""
eCdreCfg.MaskLength = 0
eCdreCfg.ExportDir = "/var/log/cgrates/cdre"
eCdreCfg.ContentFields = []*CdreCdrField{
&CdreCdrField{
Name: utils.CGRID,
Type: utils.CDRFIELD,
Value: utils.CGRID,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
eCdreCfg.ContentFields = []*CfgCdrField{
&CfgCdrField{
Tag: utils.CGRID,
Type: utils.CDRFIELD,
CdrFieldId: utils.CGRID,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.CGRID}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.MEDI_RUNID,
Type: utils.CDRFIELD,
Value: utils.MEDI_RUNID,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
&CfgCdrField{
Tag: utils.MEDI_RUNID,
Type: utils.CDRFIELD,
CdrFieldId: utils.MEDI_RUNID,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.MEDI_RUNID}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.TOR,
Type: utils.CDRFIELD,
Value: utils.TOR,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
&CfgCdrField{
Tag: utils.TOR,
Type: utils.CDRFIELD,
CdrFieldId: utils.TOR,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.TOR}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.ACCID,
Type: utils.CDRFIELD,
Value: utils.ACCID,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
&CfgCdrField{
Tag: utils.ACCID,
Type: utils.CDRFIELD,
CdrFieldId: utils.ACCID,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.ACCID}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.REQTYPE,
Type: utils.CDRFIELD,
Value: utils.REQTYPE,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
&CfgCdrField{
Tag: utils.REQTYPE,
Type: utils.CDRFIELD,
CdrFieldId: utils.REQTYPE,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.REQTYPE}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.DIRECTION,
Type: utils.CDRFIELD,
Value: utils.DIRECTION,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
&CfgCdrField{
Tag: utils.DIRECTION,
Type: utils.CDRFIELD,
CdrFieldId: utils.DIRECTION,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.DIRECTION}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.TENANT,
Type: utils.CDRFIELD,
Value: utils.TENANT,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
&CfgCdrField{
Tag: utils.TENANT,
Type: utils.CDRFIELD,
CdrFieldId: utils.TENANT,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.TENANT}},
Mandatory: true},
&CfgCdrField{
Tag: utils.CATEGORY,
Type: utils.CDRFIELD,
CdrFieldId: utils.CATEGORY,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.CATEGORY}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.CATEGORY,
Type: utils.CDRFIELD,
Value: utils.CATEGORY,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
&CfgCdrField{
Tag: utils.ACCOUNT,
Type: utils.CDRFIELD,
CdrFieldId: utils.ACCOUNT,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.ACCOUNT}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.ACCOUNT,
Type: utils.CDRFIELD,
Value: utils.ACCOUNT,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
&CfgCdrField{
Tag: utils.SUBJECT,
Type: utils.CDRFIELD,
CdrFieldId: utils.SUBJECT,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.SUBJECT}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.SUBJECT,
Type: utils.CDRFIELD,
Value: utils.SUBJECT,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
&CfgCdrField{
Tag: utils.DESTINATION,
Type: utils.CDRFIELD,
CdrFieldId: utils.DESTINATION,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.DESTINATION}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.DESTINATION,
Type: utils.CDRFIELD,
Value: utils.DESTINATION,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
&CfgCdrField{
Tag: utils.SETUP_TIME,
Type: utils.CDRFIELD,
CdrFieldId: utils.SETUP_TIME,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.SETUP_TIME}},
Layout: "2006-01-02T15:04:05Z07:00",
Mandatory: true,
},
&CdreCdrField{
Name: utils.SETUP_TIME,
Type: utils.CDRFIELD,
Value: utils.SETUP_TIME,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
&CfgCdrField{
Tag: utils.ANSWER_TIME,
Type: utils.CDRFIELD,
CdrFieldId: utils.ANSWER_TIME,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.ANSWER_TIME}},
Layout: "2006-01-02T15:04:05Z07:00",
Mandatory: true,
},
&CdreCdrField{
Name: utils.ANSWER_TIME,
Type: utils.CDRFIELD,
Value: utils.ANSWER_TIME,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
&CfgCdrField{
Tag: utils.USAGE,
Type: utils.CDRFIELD,
CdrFieldId: utils.USAGE,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.USAGE}},
Mandatory: true,
},
&CdreCdrField{
Name: utils.USAGE,
Type: utils.CDRFIELD,
Value: utils.USAGE,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
},
&CdreCdrField{
Name: utils.COST,
Type: utils.CDRFIELD,
Value: utils.COST,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.COST},
&CfgCdrField{
Tag: utils.COST,
Type: utils.CDRFIELD,
CdrFieldId: utils.COST,
Value: []*utils.RSRField{
&utils.RSRField{Id: utils.COST}},
Mandatory: true,
},
}
if cdreCfg, err := NewDefaultCdreConfig(); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCfg, cdreCfg) {
if cdreCfg := NewDefaultCdreConfig(); !reflect.DeepEqual(eCdreCfg, cdreCfg) {
for _, fld := range cdreCfg.ContentFields {
fmt.Printf("Have field: %+v\n", fld)
}
t.Errorf("Expecting: %v, received: %v", eCdreCfg, cdreCfg)
}
}
func TestCdreCfgSetDefaultFieldProperties(t *testing.T) {
cdreCdrFld := &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
}
eCdreCdrFld := &CdreCdrField{
Width: 40,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.ORDERID},
}
eCdreCdrFld = &CdreCdrField{
Width: 11,
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ORDERID},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
}
eCdreCdrFld = &CdreCdrField{
Width: 6,
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
}
eCdreCdrFld = &CdreCdrField{
Width: 36,
Strip: "left",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.CDRHOST},
}
eCdreCdrFld = &CdreCdrField{
Width: 15,
Strip: "left",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CDRHOST},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.CDRSOURCE},
}
eCdreCdrFld = &CdreCdrField{
Width: 15,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CDRSOURCE},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
}
eCdreCdrFld = &CdreCdrField{
Width: 13,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
}
eCdreCdrFld = &CdreCdrField{
Width: 4,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
}
eCdreCdrFld = &CdreCdrField{
Width: 10,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
}
eCdreCdrFld = &CdreCdrField{
Width: 30,
Strip: "xright",
Padding: "left",
Layout: "2006-01-02T15:04:05Z07:00",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
}
eCdreCdrFld = &CdreCdrField{
Width: 30,
Strip: "xright",
Padding: "left",
Layout: "2006-01-02T15:04:05Z07:00",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
}
eCdreCdrFld = &CdreCdrField{
Width: 30,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
}
eCdreCdrFld = &CdreCdrField{
Width: 20,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.COST},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.COST},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: "extra_1"},
}
eCdreCdrFld = &CdreCdrField{
Width: 30,
Strip: "xright",
Padding: "left",
Mandatory: false,
valueAsRsrField: &utils.RSRField{Id: "extra_1"},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
}

View File

@@ -88,23 +88,15 @@ type CGRConfig struct {
RaterBalancer string // balancer address host:port
BalancerEnabled bool
SchedulerEnabled bool
CDRSEnabled bool // Enable CDR Server service
CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs
CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
CDRSStats string // Address where to reach the Mediator. <""|intenal>
CDRSStoreDisable bool // When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
CDRStatsEnabled bool // Enable CDR Stats service
CDRStatConfig *CdrStatsConfig // Active cdr stats configuration instances
CdreDefaultInstance *CdreConfig // Will be used in the case no specific one selected by API
CdrcEnabled bool // Enable CDR client functionality
CdrcCdrs string // Address where to reach CDR server
CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
CdrcCdrType string // CDR file format <csv>.
CdrcCsvSep string // Separator used in case of csv files. One character only supported.
CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored.
CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved.
CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database.
CdrcCdrFields map[string][]*utils.RSRField // FieldName/RSRField format. Index number in case of .csv cdrs.
CDRSEnabled bool // Enable CDR Server service
CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs
CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
CDRSStats string // Address where to reach the Mediator. <""|intenal>
CDRSStoreDisable bool // When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
CDRStatsEnabled bool // Enable CDR Stats service
CDRStatConfig *CdrStatsConfig // Active cdr stats configuration instances
CdreDefaultInstance *CdreConfig // Will be used in the case no specific one selected by API
CdrcInstances []*CdrcConfig // Number of CDRC instances running imports
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
@@ -185,29 +177,8 @@ func (self *CGRConfig) setDefaults() error {
self.CDRSStoreDisable = false
self.CDRStatsEnabled = false
self.CDRStatConfig = NewCdrStatsConfigWithDefaults()
self.CdreDefaultInstance, _ = NewDefaultCdreConfig()
self.CdrcEnabled = false
self.CdrcCdrs = utils.INTERNAL
self.CdrcRunDelay = time.Duration(0)
self.CdrcCdrType = utils.CSV
self.CdrcCsvSep = string(utils.CSV_SEP)
self.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
self.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
self.CdrcSourceId = "csv"
self.CdrcCdrFields = map[string][]*utils.RSRField{
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}},
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}},
}
self.CdreDefaultInstance = NewDefaultCdreConfig()
self.CdrcInstances = []*CdrcConfig{NewDefaultCdrcConfig()} // This instance is just for the sake of defaults, it will be replaced when the file is loaded with the one resulted from there
self.MediatorEnabled = false
self.MediatorRater = utils.INTERNAL
self.MediatorReconnects = 3
@@ -249,15 +220,18 @@ func (self *CGRConfig) setDefaults() error {
}
func (self *CGRConfig) checkConfigSanity() error {
if self.CdrcEnabled {
if len(self.CdrcCdrFields) == 0 {
return errors.New("CdrC enabled but no fields to be processed defined!")
}
if self.CdrcCdrType == utils.CSV {
for _, rsrFldLst := range self.CdrcCdrFields {
for _, rsrFld := range rsrFldLst {
if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil {
return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id)
// CDRC sanity checks
for _, cdrcInst := range self.CdrcInstances {
if cdrcInst.Enabled == true {
if len(cdrcInst.CdrFields) == 0 {
return errors.New("CdrC enabled but no fields to be processed defined!")
}
if cdrcInst.CdrType == utils.CSV {
for _, cdrFld := range cdrcInst.CdrFields {
for _, rsrFld := range cdrFld.Value {
if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil {
return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id)
}
}
}
}
@@ -478,10 +452,12 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
exportTemplate, _ := c.GetString("cdre", "export_template")
if strings.HasPrefix(exportTemplate, utils.XML_PROFILE_PREFIX) {
if xmlTemplates := cfg.XmlCfgDocument.GetCdreCfgs(exportTemplate[len(utils.XML_PROFILE_PREFIX):]); xmlTemplates != nil {
cfg.CdreDefaultInstance = xmlTemplates[exportTemplate[len(utils.XML_PROFILE_PREFIX):]].AsCdreConfig()
if cfg.CdreDefaultInstance, err = NewCdreConfigFromXmlCdreCfg(xmlTemplates[exportTemplate[len(utils.XML_PROFILE_PREFIX):]]); err != nil {
return nil, err
}
}
} else { // Not loading out of template
if flds, err := NewCdreCdrFieldsFromIds(cfg.CdreDefaultInstance.CdrFormat == utils.CDRE_FIXED_WIDTH,
if flds, err := NewCfgCdrFieldsFromIds(cfg.CdreDefaultInstance.CdrFormat == utils.CDRE_FIXED_WIDTH,
strings.Split(exportTemplate, string(utils.CSV_SEP))...); err != nil {
return nil, err
} else {
@@ -492,53 +468,19 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("cdre", "export_dir"); hasOpt {
cfg.CdreDefaultInstance.ExportDir, _ = c.GetString("cdre", "export_dir")
}
if hasOpt = c.HasOption("cdrc", "enabled"); hasOpt {
cfg.CdrcEnabled, _ = c.GetBool("cdrc", "enabled")
// CDRC Default instance parsing
if cdrcFileCfgInst, err := NewCdrcConfigFromFileParams(c); err != nil {
return nil, err
} else {
cfg.CdrcInstances = []*CdrcConfig{cdrcFileCfgInst}
}
if hasOpt = c.HasOption("cdrc", "cdrs"); hasOpt {
cfg.CdrcCdrs, _ = c.GetString("cdrc", "cdrs")
}
if hasOpt = c.HasOption("cdrc", "run_delay"); hasOpt {
durStr, _ := c.GetString("cdrc", "run_delay")
if cfg.CdrcRunDelay, err = utils.ParseDurationWithSecs(durStr); err != nil {
return nil, err
}
}
if hasOpt = c.HasOption("cdrc", "cdr_type"); hasOpt {
cfg.CdrcCdrType, _ = c.GetString("cdrc", "cdr_type")
}
if hasOpt = c.HasOption("cdrc", "csv_separator"); hasOpt {
cfg.CdrcCsvSep, _ = c.GetString("cdrc", "csv_separator")
}
if hasOpt = c.HasOption("cdrc", "cdr_in_dir"); hasOpt {
cfg.CdrcCdrInDir, _ = c.GetString("cdrc", "cdr_in_dir")
}
if hasOpt = c.HasOption("cdrc", "cdr_out_dir"); hasOpt {
cfg.CdrcCdrOutDir, _ = c.GetString("cdrc", "cdr_out_dir")
}
if hasOpt = c.HasOption("cdrc", "cdr_source_id"); hasOpt {
cfg.CdrcSourceId, _ = c.GetString("cdrc", "cdr_source_id")
}
// ParseCdrcCdrFields
torIdFld, _ := c.GetString("cdrc", "tor_field")
accIdFld, _ := c.GetString("cdrc", "accid_field")
reqtypeFld, _ := c.GetString("cdrc", "reqtype_field")
directionFld, _ := c.GetString("cdrc", "direction_field")
tenantFld, _ := c.GetString("cdrc", "tenant_field")
categoryFld, _ := c.GetString("cdrc", "category_field")
acntFld, _ := c.GetString("cdrc", "account_field")
subjectFld, _ := c.GetString("cdrc", "subject_field")
destFld, _ := c.GetString("cdrc", "destination_field")
setupTimeFld, _ := c.GetString("cdrc", "setup_time_field")
answerTimeFld, _ := c.GetString("cdrc", "answer_time_field")
durFld, _ := c.GetString("cdrc", "usage_field")
extraFlds, _ := c.GetString("cdrc", "extra_fields")
if len(torIdFld) != 0 || len(accIdFld) != 0 || len(reqtypeFld) != 0 || len(directionFld) != 0 || len(tenantFld) != 0 || len(categoryFld) != 0 || len(acntFld) != 0 ||
len(subjectFld) != 0 || len(destFld) != 0 || len(setupTimeFld) != 0 || len(answerTimeFld) != 0 || len(durFld) != 0 || len(extraFlds) != 0 {
// We overwrite the defaults only if at least one of the fields were defined
if cfg.CdrcCdrFields, err = ParseCdrcCdrFields(torIdFld, accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld,
setupTimeFld, answerTimeFld, durFld, extraFlds); err != nil {
return nil, err
if cfg.XmlCfgDocument != nil { // Add the possible configured instances inside xml doc
for id, xmlCdrcCfg := range cfg.XmlCfgDocument.GetCdrcCfgs("") {
if cdrcInst, err := NewCdrcConfigFromCgrXmlCdrcCfg(id, xmlCdrcCfg); err != nil {
return nil, err
} else {
cfg.CdrcInstances = append(cfg.CdrcInstances, cdrcInst)
}
}
}
if hasOpt = c.HasOption("mediator", "enabled"); hasOpt {

View File

@@ -79,7 +79,8 @@ func TestDefaults(t *testing.T) {
eCfg.RaterBalancer = ""
eCfg.BalancerEnabled = false
eCfg.SchedulerEnabled = false
eCfg.CdreDefaultInstance, _ = NewDefaultCdreConfig()
eCfg.CdreDefaultInstance = NewDefaultCdreConfig()
eCfg.CdrcInstances = []*CdrcConfig{NewDefaultCdrcConfig()}
eCfg.CDRSEnabled = false
eCfg.CDRSExtraFields = []*utils.RSRField{}
eCfg.CDRSMediator = ""
@@ -87,28 +88,6 @@ func TestDefaults(t *testing.T) {
eCfg.CDRSStoreDisable = false
eCfg.CDRStatsEnabled = false
eCfg.CDRStatConfig = &CdrStatsConfig{Id: utils.META_DEFAULT, QueueLength: 50, TimeWindow: time.Duration(1) * time.Hour, Metrics: []string{"ASR", "ACD", "ACC"}}
eCfg.CdrcEnabled = false
eCfg.CdrcCdrs = utils.INTERNAL
eCfg.CdrcRunDelay = time.Duration(0)
eCfg.CdrcCdrType = "csv"
eCfg.CdrcCsvSep = string(utils.CSV_SEP)
eCfg.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
eCfg.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
eCfg.CdrcSourceId = "csv"
eCfg.CdrcCdrFields = map[string][]*utils.RSRField{
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}},
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}},
}
eCfg.MediatorEnabled = false
eCfg.MediatorRater = utils.INTERNAL
eCfg.MediatorReconnects = 3
@@ -164,20 +143,6 @@ func TestSanityCheck(t *testing.T) {
t.Error("Invalid defaults: ", err)
}
cfg = &CGRConfig{}
cfg.CdrcEnabled = true
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect missing CDR fields definition")
}
cfg.CdrcCdrType = utils.CSV
cfg.CdrcCdrFields = map[string][]*utils.RSRField{utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}}}
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect improper use of CDR field names")
}
cfg.CdrcCdrFields = map[string][]*utils.RSRField{"extra1": []*utils.RSRField{&utils.RSRField{Id: "test"}}}
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect improper use of CDR field names")
}
cfg = &CGRConfig{}
cfg.CDRSStats = utils.INTERNAL
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed detecting improper CDRStats configuration within CDRS")
@@ -258,30 +223,32 @@ func TestConfigFromFile(t *testing.T) {
MaskDestId: "test",
MaskLength: 99,
ExportDir: "test"}
eCfg.CdreDefaultInstance.ContentFields, _ = NewCdreCdrFieldsFromIds(false, "test")
eCfg.CdrcEnabled = true
eCfg.CdrcCdrs = "test"
eCfg.CdrcRunDelay = time.Duration(99) * time.Second
eCfg.CdrcCdrType = "test"
eCfg.CdrcCsvSep = ";"
eCfg.CdrcCdrInDir = "test"
eCfg.CdrcCdrOutDir = "test"
eCfg.CdrcSourceId = "test"
eCfg.CdrcCdrFields = map[string][]*utils.RSRField{
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "test"}},
"test": []*utils.RSRField{&utils.RSRField{Id: "test"}},
eCfg.CdreDefaultInstance.ContentFields, _ = NewCfgCdrFieldsFromIds(false, "test")
cdrcCfg := NewDefaultCdrcConfig()
cdrcCfg.Enabled = true
cdrcCfg.CdrsAddress = "test"
cdrcCfg.RunDelay = time.Duration(99) * time.Second
cdrcCfg.CdrType = "test"
cdrcCfg.FieldSeparator = ";"
cdrcCfg.CdrInDir = "test"
cdrcCfg.CdrOutDir = "test"
cdrcCfg.CdrSourceId = "test"
cdrcCfg.CdrFields = []*CfgCdrField{
&CfgCdrField{Tag: utils.TOR, Type: utils.CDRFIELD, CdrFieldId: utils.TOR, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.ACCID, Type: utils.CDRFIELD, CdrFieldId: utils.ACCID, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.REQTYPE, Type: utils.CDRFIELD, CdrFieldId: utils.REQTYPE, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.DIRECTION, Type: utils.CDRFIELD, CdrFieldId: utils.DIRECTION, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.TENANT, Type: utils.CDRFIELD, CdrFieldId: utils.TENANT, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.CATEGORY, Type: utils.CDRFIELD, CdrFieldId: utils.CATEGORY, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.ACCOUNT, Type: utils.CDRFIELD, CdrFieldId: utils.ACCOUNT, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.SUBJECT, Type: utils.CDRFIELD, CdrFieldId: utils.SUBJECT, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.DESTINATION, Type: utils.CDRFIELD, CdrFieldId: utils.DESTINATION, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: utils.SETUP_TIME, Type: utils.CDRFIELD, CdrFieldId: utils.SETUP_TIME, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true, Layout: "2006-01-02T15:04:05Z07:00"},
&CfgCdrField{Tag: utils.ANSWER_TIME, Type: utils.CDRFIELD, CdrFieldId: utils.ANSWER_TIME, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true, Layout: "2006-01-02T15:04:05Z07:00"},
&CfgCdrField{Tag: utils.USAGE, Type: utils.CDRFIELD, CdrFieldId: utils.USAGE, Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}, Mandatory: true},
&CfgCdrField{Tag: "test", Type: utils.CDRFIELD, CdrFieldId: "test", Value: []*utils.RSRField{&utils.RSRField{Id: "test"}}},
}
eCfg.CdrcInstances = []*CdrcConfig{cdrcCfg}
eCfg.MediatorEnabled = true
eCfg.MediatorRater = "test"
eCfg.MediatorReconnects = 99
@@ -325,6 +292,7 @@ func TestConfigFromFile(t *testing.T) {
t.Log(cfg)
t.Error("Loading of configuration from file failed!")
}
}
func TestCdrsExtraFields(t *testing.T) {
@@ -359,12 +327,10 @@ func TestCdreExtraFields(t *testing.T) {
cdr_format = csv
export_template = cgrid,mediation_runid,accid
`)
expectedFlds := []*CdreCdrField{
&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"},
Mandatory: true},
&CdreCdrField{Name: "accid", Type: utils.CDRFIELD, Value: "accid", valueAsRsrField: &utils.RSRField{Id: "accid"},
Mandatory: true},
expectedFlds := []*CfgCdrField{
&CfgCdrField{Tag: "cgrid", Type: utils.CDRFIELD, CdrFieldId: "cgrid", Value: []*utils.RSRField{&utils.RSRField{Id: "cgrid"}}, Mandatory: true},
&CfgCdrField{Tag: "mediation_runid", Type: utils.CDRFIELD, CdrFieldId: "mediation_runid", Value: []*utils.RSRField{&utils.RSRField{Id: "mediation_runid"}}, Mandatory: true},
&CfgCdrField{Tag: "accid", Type: utils.CDRFIELD, CdrFieldId: "accid", Value: []*utils.RSRField{&utils.RSRField{Id: "accid"}}, Mandatory: true},
}
expCdreCfg := &CdreConfig{CdrFormat: utils.CSV, FieldSeparator: utils.CSV_SEP, CostRoundingDecimals: -1, ExportDir: "/var/log/cgrates/cdre", ContentFields: expectedFlds}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
@@ -377,10 +343,9 @@ cdr_format = csv
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"}, 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}}
expectedFlds = []*CfgCdrField{
&CfgCdrField{Tag: "cgrid", Type: utils.CDRFIELD, CdrFieldId: "cgrid", Value: []*utils.RSRField{&utils.RSRField{Id: "cgrid"}}, Mandatory: true},
&CfgCdrField{Tag: "effective_caller_id_number", Type: utils.CDRFIELD, CdrFieldId: "effective_caller_id_number", Value: []*utils.RSRField{rsrField}, Mandatory: false}}
expCdreCfg.ContentFields = expectedFlds
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
@@ -395,15 +360,3 @@ export_template = cgrid,~accid:s/(\d)/$1,runid
t.Error("Failed to detect failed RSRParsing")
}
}
func TestCdrcCdrDefaultFields(t *testing.T) {
cdrcCfg := []byte(`[cdrc]
enabled = true
`)
cfgDefault, _ := NewDefaultCGRConfig()
if cfg, err := NewCGRConfigFromBytes(cdrcCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.CdrcCdrFields, cfgDefault.CdrcCdrFields) {
t.Errorf("Unexpected value for CdrcCdrFields: %v", cfg.CdrcCdrFields)
}
}

View File

@@ -100,22 +100,6 @@ usage_fields = test1, test2
}
func TestParseCdrcCdrFields(t *testing.T) {
eFieldsCfg := []byte(`[cdrc]
cdr_type = test
tor_field = tor1
accid_field = accid1
reqtype_field = reqtype1
direction_field = direction1
tenant_field = tenant1
category_field = category1
account_field = account1
subject_field = subject1
destination_field = destination1
setup_time_field = setuptime1
answer_time_field = answertime1
usage_field = duration1
extra_fields = extra1:extraval1,extra2:extraval1
`)
eCdrcCdrFlds := map[string][]*utils.RSRField{
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "tor1"}},
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "accid1"}},
@@ -132,9 +116,10 @@ extra_fields = extra1:extraval1,extra2:extraval1
"extra1": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}},
"extra2": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}},
}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
if cdrFlds, err := ParseCdrcCdrFields("tor1", "accid1", "reqtype1", "direction1", "tenant1", "category1", "account1", "subject1", "destination1",
"setuptime1", "answertime1", "duration1", "extra1:extraval1,extra2:extraval1"); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.CdrcCdrFields, eCdrcCdrFlds) {
t.Errorf("Expecting: %v, received: %v, tor: %v", eCdrcCdrFlds, cfg.CdrcCdrFields, cfg.CdrcCdrFields[utils.TOR])
} else if !reflect.DeepEqual(eCdrcCdrFlds, cdrFlds) {
t.Errorf("Expecting: %v, received: %v, tor: %v", eCdrcCdrFlds, cdrFlds, cdrFlds[utils.TOR])
}
}

View File

@@ -1,87 +0,0 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"encoding/xml"
"github.com/cgrates/cgrates/utils"
)
type CgrXmlCdrcCfg struct {
Enabled bool `xml:"enabled"` // Enable/Disable the
CdrsAddress string `xml:"cdrs_address"` // The address where CDRs can be reached
CdrType string `xml:"cdr_type"` // The type of CDR to process <csv>
CsvSeparator string `xml:"field_separator"` // The separator to use when reading csvs
RunDelay int64 `xml:"run_delay"` // Delay between runs
CdrInDir string `xml:"cdr_in_dir"` // Folder to process CDRs from
CdrOutDir string `xml:"cdr_out_dir"` // Folder to move processed CDRs to
CdrSourceId string `xml:"cdr_source_id"` // Source identifier for the processed CDRs
CdrFields []*CdrcField `xml:"fields>field"`
}
// Set the defaults
func (cdrcCfg *CgrXmlCdrcCfg) setDefaults() error {
dfCfg, _ := NewDefaultCGRConfig()
if len(cdrcCfg.CdrsAddress) == 0 {
cdrcCfg.CdrsAddress = dfCfg.CdrcCdrs
}
if len(cdrcCfg.CdrType) == 0 {
cdrcCfg.CdrType = dfCfg.CdrcCdrType
}
if len(cdrcCfg.CsvSeparator) == 0 {
cdrcCfg.CsvSeparator = dfCfg.CdrcCsvSep
}
if len(cdrcCfg.CdrInDir) == 0 {
cdrcCfg.CdrInDir = dfCfg.CdrcCdrInDir
}
if len(cdrcCfg.CdrOutDir) == 0 {
cdrcCfg.CdrOutDir = dfCfg.CdrcCdrOutDir
}
if len(cdrcCfg.CdrSourceId) == 0 {
cdrcCfg.CdrSourceId = dfCfg.CdrcSourceId
}
if len(cdrcCfg.CdrFields) == 0 {
for key, cfgRsrFields := range dfCfg.CdrcCdrFields {
cdrcCfg.CdrFields = append(cdrcCfg.CdrFields, &CdrcField{Id: key, Value: "PLACEHOLDER", rsrFields: cfgRsrFields})
}
}
return nil
}
func (cdrcCfg *CgrXmlCdrcCfg) CdrRSRFields() map[string][]*utils.RSRField {
rsrFields := make(map[string][]*utils.RSRField)
for _, fld := range cdrcCfg.CdrFields {
rsrFields[fld.Id] = fld.rsrFields
}
return rsrFields
}
type CdrcField struct {
XMLName xml.Name `xml:"field"`
Id string `xml:"id,attr"`
Value string `xml:"value,attr"`
rsrFields []*utils.RSRField
}
func (cdrcFld *CdrcField) PopulateRSRFields() (err error) {
if cdrcFld.rsrFields, err = utils.ParseRSRFields(cdrcFld.Value, utils.INFIELD_SEP); err != nil {
return err
}
return nil
}

View File

@@ -28,21 +28,7 @@ import (
var cfgDocCdrc *CgrXmlCfgDocument // Will be populated by first test
func TestPopulateRSRFIeld(t *testing.T) {
cdrcField := CdrcField{Id: "TEST1", Value: `~effective_caller_id_number:s/(\d+)/+$1/`}
if err := cdrcField.PopulateRSRFields(); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if cdrcField.rsrFields == nil {
t.Error("Failed loading the RSRField")
}
cdrcField = CdrcField{Id: "TEST2", Value: `99`}
if err := cdrcField.PopulateRSRFields(); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if cdrcField.rsrFields == nil {
t.Error("Failed loading the RSRField")
}
}
/*
func TestSetDefaults(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
@@ -73,33 +59,34 @@ func TestSetDefaults(t *testing.T) {
t.Error("Failed loading default configuration")
}
}
*/
func TestParseXmlCdrcConfig(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdrc" type="csv" id="CDRC-CSV1">
<configuration section="cdrc" id="CDRC-CSV1">
<enabled>true</enabled>
<cdrs_address>internal</cdrs_address>
<cdr_type>csv</cdr_type>
<csv_separator>,</csv_separator>
<field_separator>,</field_separator>
<run_delay>0</run_delay>
<cdr_in_dir>/var/log/cgrates/cdrc/in</cdr_in_dir>
<cdr_out_dir>/var/log/cgrates/cdrc/out</cdr_out_dir>
<cdr_source_id>freeswitch_csv</cdr_source_id>
<fields>
<field id="accid" value="0;13" />
<field id="reqtype" value="1" />
<field id="direction" value="2" />
<field id="tenant" value="3" />
<field id="category" value="4" />
<field id="account" value="5" />
<field id="subject" value="6" />
<field id="destination" value="7" />
<field id="setup_time" value="8" />
<field id="answer_time" value="9" />
<field id="usage" value="10" />
<field id="extr1" value="11" />
<field id="extr2" value="12" />
<field tag="accid" value="0;13" />
<field tag="reqtype" value="1" />
<field tag="direction" value="2" />
<field tag="tenant" value="3" />
<field tag="category" value="4" />
<field tag="account" value="5" />
<field tag="subject" value="6" />
<field tag="destination" value="7" />
<field tag="setup_time" value="8" />
<field tag="answer_time" value="9" />
<field tag="usage" value="10" />
<field tag="extr1" value="11" />
<field tag="extr2" value="12" />
</fields>
</configuration>
</document>`
@@ -120,52 +107,35 @@ func TestGetCdrcCfgs(t *testing.T) {
if cdrcfgs == nil {
t.Error("No config instance returned")
}
expectCdrc := &CgrXmlCdrcCfg{Enabled: true, CdrsAddress: "internal", CdrType: "csv", CsvSeparator: ",",
RunDelay: 0, CdrInDir: "/var/log/cgrates/cdrc/in", CdrOutDir: "/var/log/cgrates/cdrc/out", CdrSourceId: "freeswitch_csv"}
cdrFlds := []*CdrcField{
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCID, Value: "0;13"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.REQTYPE, Value: "1"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DIRECTION, Value: "2"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.TENANT, Value: "3"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.CATEGORY, Value: "4"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCOUNT, Value: "5"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.SUBJECT, Value: "6"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DESTINATION, Value: "7"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.SETUP_TIME, Value: "8"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ANSWER_TIME, Value: "9"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.USAGE, Value: "10"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr1", Value: "11"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr2", Value: "12"}}
for _, fld := range cdrFlds {
fld.PopulateRSRFields()
}
enabled := true
cdrsAddr := "internal"
cdrType := "csv"
fldSep := ","
runDelay := int64(0)
cdrInDir := "/var/log/cgrates/cdrc/in"
cdrOutDir := "/var/log/cgrates/cdrc/out"
cdrSrcId := "freeswitch_csv"
expectCdrc := &CgrXmlCdrcCfg{Enabled: &enabled, CdrsAddress: &cdrsAddr, CdrType: &cdrType, FieldSeparator: &fldSep,
RunDelay: &runDelay, CdrInDir: &cdrInDir, CdrOutDir: &cdrOutDir, CdrSourceId: &cdrSrcId}
accIdTag, reqTypeTag, dirTag, tntTag, categTag, acntTag, subjTag, dstTag, sTimeTag, aTimeTag, usageTag, extr1, extr2 := utils.ACCID,
utils.REQTYPE, utils.DIRECTION, utils.TENANT, utils.CATEGORY, utils.ACCOUNT, utils.SUBJECT, utils.DESTINATION, utils.SETUP_TIME, utils.ANSWER_TIME, utils.USAGE, "extr1", "extr2"
accIdVal, reqVal, dirVal, tntVal, categVal, acntVal, subjVal, dstVal, sTimeVal, aTimeVal, usageVal, extr1Val, extr2Val := "0;13", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"
cdrFlds := []*XmlCfgCdrField{
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &accIdTag, Value: &accIdVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &reqTypeTag, Value: &reqVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &dirTag, Value: &dirVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &tntTag, Value: &tntVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &categTag, Value: &categVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &acntTag, Value: &acntVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &subjTag, Value: &subjVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &dstTag, Value: &dstVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &sTimeTag, Value: &sTimeVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &aTimeTag, Value: &aTimeVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &usageTag, Value: &usageVal},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &extr1, Value: &extr1Val},
&XmlCfgCdrField{XMLName: xml.Name{Local: "field"}, Tag: &extr2, Value: &extr2Val}}
expectCdrc.CdrFields = cdrFlds
if !reflect.DeepEqual(expectCdrc, cdrcfgs["CDRC-CSV1"]) {
t.Errorf("Expecting: %v, received: %v", expectCdrc, cdrcfgs["CDRC-CSV1"])
}
}
func TestCdrRSRFields(t *testing.T) {
cdrcfgs := cfgDocCdrc.GetCdrcCfgs("CDRC-CSV1")
if cdrcfgs == nil {
t.Error("No config instance returned")
}
eRSRFields := map[string][]*utils.RSRField{
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "0"}, &utils.RSRField{Id: "13"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "1"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "2"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "3"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "4"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "5"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "7"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "8"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "9"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "10"}},
"extr1": []*utils.RSRField{&utils.RSRField{Id: "11"}},
"extr2": []*utils.RSRField{&utils.RSRField{Id: "12"}},
}
if rsrFields := cdrcfgs["CDRC-CSV1"].CdrRSRFields(); !reflect.DeepEqual(rsrFields, eRSRFields) {
t.Errorf("Expecting: %v, received: %v", eRSRFields, rsrFields)
}
}

View File

@@ -1,154 +0,0 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"encoding/xml"
"github.com/cgrates/cgrates/utils"
)
// The CdrExporter configuration instance
type CgrXmlCdreCfg struct {
CdrFormat *string `xml:"cdr_format"`
FieldSeparator *string `xml:"field_separator"`
DataUsageMultiplyFactor *float64 `xml:"data_usage_multiply_factor"`
CostMultiplyFactor *float64 `xml:"cost_multiply_factor"`
CostRoundingDecimals *int `xml:"cost_rounding_decimals"`
CostShiftDigits *int `xml:"cost_shift_digits"`
MaskDestId *string `xml:"mask_destination_id"`
MaskLength *int `xml:"mask_length"`
ExportDir *string `xml:"export_dir"`
Header *CgrXmlCfgCdrHeader `xml:"export_template>header"`
Content *CgrXmlCfgCdrContent `xml:"export_template>content"`
Trailer *CgrXmlCfgCdrTrailer `xml:"export_template>trailer"`
}
func (xmlCdreCfg *CgrXmlCdreCfg) AsCdreConfig() *CdreConfig {
cdreCfg, _ := NewDefaultCdreConfig()
if xmlCdreCfg.CdrFormat != nil {
cdreCfg.CdrFormat = *xmlCdreCfg.CdrFormat
}
if xmlCdreCfg.FieldSeparator != nil && len(*xmlCdreCfg.FieldSeparator) == 1 {
sepStr := *xmlCdreCfg.FieldSeparator
cdreCfg.FieldSeparator = rune(sepStr[0])
}
if xmlCdreCfg.DataUsageMultiplyFactor != nil {
cdreCfg.DataUsageMultiplyFactor = *xmlCdreCfg.DataUsageMultiplyFactor
}
if xmlCdreCfg.CostMultiplyFactor != nil {
cdreCfg.CostMultiplyFactor = *xmlCdreCfg.CostMultiplyFactor
}
if xmlCdreCfg.CostRoundingDecimals != nil {
cdreCfg.CostRoundingDecimals = *xmlCdreCfg.CostRoundingDecimals
}
if xmlCdreCfg.CostShiftDigits != nil {
cdreCfg.CostShiftDigits = *xmlCdreCfg.CostShiftDigits
}
if xmlCdreCfg.MaskDestId != nil {
cdreCfg.MaskDestId = *xmlCdreCfg.MaskDestId
}
if xmlCdreCfg.MaskLength != nil {
cdreCfg.MaskLength = *xmlCdreCfg.MaskLength
}
if xmlCdreCfg.ExportDir != nil {
cdreCfg.ExportDir = *xmlCdreCfg.ExportDir
}
if xmlCdreCfg.Header != nil {
cdreCfg.HeaderFields = make([]*CdreCdrField, len(xmlCdreCfg.Header.Fields))
for idx, xmlFld := range xmlCdreCfg.Header.Fields {
cdreCfg.HeaderFields[idx] = xmlFld.AsCdreCdrField()
}
}
if xmlCdreCfg.Content != nil {
cdreCfg.ContentFields = make([]*CdreCdrField, len(xmlCdreCfg.Content.Fields))
for idx, xmlFld := range xmlCdreCfg.Content.Fields {
cdreCfg.ContentFields[idx] = xmlFld.AsCdreCdrField()
}
}
if xmlCdreCfg.Trailer != nil {
cdreCfg.TrailerFields = make([]*CdreCdrField, len(xmlCdreCfg.Trailer.Fields))
for idx, xmlFld := range xmlCdreCfg.Trailer.Fields {
cdreCfg.TrailerFields[idx] = xmlFld.AsCdreCdrField()
}
}
return cdreCfg
}
// CDR header
type CgrXmlCfgCdrHeader struct {
XMLName xml.Name `xml:"header"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR content
type CgrXmlCfgCdrContent struct {
XMLName xml.Name `xml:"content"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR trailer
type CgrXmlCfgCdrTrailer struct {
XMLName xml.Name `xml:"trailer"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR field
type CgrXmlCfgCdrField struct {
XMLName xml.Name `xml:"field"`
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
Value string `xml:"value,attr"`
Width int `xml:"width,attr"` // Field width
Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright>
Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right>
Layout string `xml:"layout,attr"` // Eg. time format layout
Filter string `xml:"filter,attr"` // Eg. combimed filters
Mandatory bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported
valueAsRsrField *utils.RSRField // Cached if the need arrises
filterAsRsrField *utils.RSRField
}
func (cdrFld *CgrXmlCfgCdrField) populateRSRField() (err error) {
cdrFld.valueAsRsrField, err = utils.NewRSRField(cdrFld.Value)
return err
}
func (cdrFld *CgrXmlCfgCdrField) populateFltrRSRField() (err error) {
cdrFld.filterAsRsrField, err = utils.NewRSRField(cdrFld.Filter)
return err
}
func (cdrFld *CgrXmlCfgCdrField) ValueAsRSRField() *utils.RSRField {
return cdrFld.valueAsRsrField
}
func (cdrFld *CgrXmlCfgCdrField) AsCdreCdrField() *CdreCdrField {
return &CdreCdrField{
Name: cdrFld.Name,
Type: cdrFld.Type,
Value: cdrFld.Value,
Width: cdrFld.Width,
Strip: cdrFld.Strip,
Padding: cdrFld.Padding,
Layout: cdrFld.Layout,
Filter: cdrFld.filterAsRsrField,
Mandatory: cdrFld.Mandatory,
valueAsRsrField: cdrFld.valueAsRsrField,
}
}

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package config
import (
"fmt"
"github.com/cgrates/cgrates/utils"
"reflect"
"strings"
@@ -28,25 +27,6 @@ import (
var cfgDoc *CgrXmlCfgDocument // Will be populated by first test
func TestXmlCdreCfgPopulateCdreRSRFIeld(t *testing.T) {
cdreField := CgrXmlCfgCdrField{Name: "TEST1", Type: "cdrfield", Value: `~effective_caller_id_number:s/(\d+)/+$1/`}
if err := cdreField.populateRSRField(); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if cdreField.valueAsRsrField == nil {
t.Error("Failed loading the RSRField")
}
valRSRField, _ := utils.NewRSRField(`~effective_caller_id_number:s/(\d+)/+$1/`)
if recv := cdreField.ValueAsRSRField(); !reflect.DeepEqual(valRSRField, recv) {
t.Errorf("Expecting %v, received %v", valRSRField, recv)
}
/*cdreField = CgrXmlCfgCdrField{Name: "TEST1", Type: "constant", Value: `someval`}
if err := cdreField.populateRSRField(); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if cdreField.valueAsRsrField != nil {
t.Error("Should not load the RSRField")
}*/
}
func TestXmlCdreCfgParseXmlConfig(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
@@ -62,51 +42,51 @@ func TestXmlCdreCfgParseXmlConfig(t *testing.T) {
<export_template>
<header>
<fields>
<field name="TypeOfRecord" type="constant" value="10" width="2" />
<field name="Filler1" type="filler" width="3" />
<field name="DistributorCode" type="constant" value="VOI" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field name="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
<field name="FileCreationfTime" type="metatag" value="time_now" layout="020106150400" width="12" />
<field name="Version" type="constant" value="01" width="2" />
<field name="Filler2" type="filler" width="105" />
<field tag="TypeOfRecord" type="constant" value="10" width="2" />
<field tag="Filler1" type="filler" width="3" />
<field tag="DistributorCode" type="constant" value="VOI" width="3" />
<field tag="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field tag="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
<field tag="FileCreationfTime" type="metatag" value="time_now" layout="020106150400" width="12" />
<field tag="Version" type="constant" value="01" width="2" />
<field tag="Filler2" type="filler" width="105" />
</fields>
</header>
<content>
<fields>
<field name="TypeOfRecord" type="constant" value="20" width="2" />
<field name="Account" type="cdrfield" value="cgrid" width="12" mandatory="true" />
<field name="Subject" type="cdrfield" value="subject" strip="left" padding="left" width="5" />
<field name="CLI" type="cdrfield" value="cli" strip="xright" width="15" />
<field name="Destination" type="cdrfield" value="destination" strip="xright" width="24" />
<field name="TOR" type="constant" value="02" width="2" />
<field name="SubtypeTOR" type="constant" value="11" width="4" />
<field name="SetupTime" type="cdrfield" value="start_time" layout="020106150400" width="12" />
<field name="Duration" type="cdrfield" value="duration" width="6" multiply_factor_voice="1000" />
<field name="DataVolume" type="filler" width="6" />
<field name="TaxCode" type="constant" value="1" width="1" />
<field name="OperatorCode" type="cdrfield" value="operator" width="2" />
<field name="ProductId" type="cdrfield" value="productid" width="5" />
<field name="NetworkId" type="constant" value="3" width="1" />
<field name="CallId" type="cdrfield" value="accid" width="16" />
<field name="Filler" type="filler" width="8" />
<field name="Filler" type="filler" width="8" />
<field name="TerminationCode" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\s\s\s\s\s)&quot;/$1/" width="5" />
<field name="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9" />
<field name="CalledMask" type="cdrfield" value="calledmask" width="1" />
<field tag="TypeOfRecord" type="constant" value="20" width="2" />
<field tag="Account" type="cdrfield" value="cgrid" width="12" mandatory="true" />
<field tag="Subject" type="cdrfield" value="subject" strip="left" padding="left" width="5" />
<field tag="CLI" type="cdrfield" value="cli" strip="xright" width="15" />
<field tag="Destination" type="cdrfield" value="destination" strip="xright" width="24" />
<field tag="TOR" type="constant" value="02" width="2" />
<field tag="SubtypeTOR" type="constant" value="11" width="4" />
<field tag="SetupTime" type="cdrfield" value="start_time" layout="020106150400" width="12" />
<field tag="Duration" type="cdrfield" value="duration" width="6" multiply_factor_voice="1000" />
<field tag="DataVolume" type="filler" width="6" />
<field tag="TaxCode" type="constant" value="1" width="1" />
<field tag="OperatorCode" type="cdrfield" value="operator" width="2" />
<field tag="ProductId" type="cdrfield" value="productid" width="5" />
<field tag="NetworkId" type="constant" value="3" width="1" />
<field tag="CallId" type="cdrfield" value="accid" width="16" />
<field tag="Filler" type="filler" width="8" />
<field tag="Filler" type="filler" width="8" />
<field tag="TerminationCode" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\s\s\s\s\s)&quot;/$1/" width="5" />
<field tag="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9" />
<field tag="CalledMask" type="cdrfield" value="calledmask" width="1" />
</fields>
</content>
<trailer>
<fields>
<field name="TypeOfRecord" type="constant" value="90" width="2" />
<field name="Filler1" type="filler" width="3" />
<field name="DistributorCode" type="constant" value="VOI" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field name="NumberOfRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6" />
<field name="CdrsDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8" />
<field name="FirstCdrTime" type="metatag" value="first_cdr_time" layout="020106150400" width="12" />
<field name="LastCdrTime" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
<field name="Filler1" type="filler" width="93" />
<field tag="TypeOfRecord" type="constant" value="90" width="2" />
<field tag="Filler1" type="filler" width="3" />
<field tag="DistributorCode" type="constant" value="VOI" width="3" />
<field tag="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field tag="NumberOfRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6" />
<field tag="CdrsDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8" />
<field tag="FirstCdrTime" type="metatag" value="first_cdr_time" layout="020106150400" width="12" />
<field tag="LastCdrTime" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
<field tag="Filler1" type="filler" width="93" />
</fields>
</trailer>
</export_template>
@@ -115,12 +95,12 @@ func TestXmlCdreCfgParseXmlConfig(t *testing.T) {
<export_template>
<content>
<fields>
<field name="CGRID" type="cdrfield" value="cgrid" width="40"/>
<field name="RatingSubject" type="cdrfield" value="subject" width="24" padding="left" strip="xright" mandatory="true"/>
<field name="Usage" type="cdrfield" value="usage" layout="seconds" width="6" padding="right" mandatory="true"/>
<field name="AccountReference" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" mandatory="true" />
<field name="AccountType" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" mandatory="true" />
<field name="MultipleMed1" type="combimed" value="cost" strip="xright" padding="left" mandatory="true" filter="~mediation_runid:s/DEFAULT/SECOND_RUN/"/>
<field tag="CGRID" type="cdrfield" value="cgrid" width="40"/>
<field tag="RatingSubject" type="cdrfield" value="subject" width="24" padding="left" strip="xright" mandatory="true"/>
<field tag="Usage" type="cdrfield" value="usage" layout="seconds" width="6" padding="right" mandatory="true"/>
<field tag="AccountReference" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" mandatory="true" />
<field tag="AccountType" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" mandatory="true" />
<field tag="MultipleMed1" type="combimed" value="cost" strip="xright" padding="left" mandatory="true" filter="~mediation_runid:s/DEFAULT/SECOND_RUN/"/>
</fields>
</content>
</export_template>
@@ -162,7 +142,7 @@ func TestXmlCdreCfgGetCdreCfg(t *testing.T) {
}
}
func TestXmlCdreCfgAsCdreConfig(t *testing.T) {
func TestNewCdreConfigFromXmlCdreCfg(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdre" type="fixed_width" id="CDRE-FW2">
@@ -178,23 +158,23 @@ func TestXmlCdreCfgAsCdreConfig(t *testing.T) {
<export_template>
<header>
<fields>
<field name="TypeOfRecord" type="constant" value="10" width="2" />
<field name="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
<field tag="TypeOfRecord" type="constant" value="10" width="2" />
<field tag="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
</fields>
</header>
<content>
<fields>
<field name="OperatorCode" type="cdrfield" value="operator" width="2" />
<field name="ProductId" type="cdrfield" value="productid" width="5" />
<field name="NetworkId" type="constant" value="3" width="1" />
<field name="FromHttpPost1" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" />
<field name="CombiMed1" type="combimed" value="cost" width="10" strip="xright" padding="left" filter="~mediation_runid:s/DEFAULT/SECOND_RUN/"/>
<field tag="OperatorCode" type="cdrfield" value="operator" width="2" />
<field tag="ProductId" type="cdrfield" value="productid" width="5" />
<field tag="NetworkId" type="constant" value="3" width="1" />
<field tag="FromHttpPost1" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" />
<field tag="CombiMed1" type="combimed" value="cost" width="10" strip="xright" padding="left" filter="~mediation_runid:s/DEFAULT/SECOND_RUN/"/>
</fields>
</content>
<trailer>
<fields>
<field name="DistributorCode" type="constant" value="VOI" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field tag="DistributorCode" type="constant" value="VOI" width="3" />
<field tag="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
</fields>
</trailer>
</export_template>
@@ -222,85 +202,99 @@ func TestXmlCdreCfgAsCdreConfig(t *testing.T) {
MaskLength: 1,
ExportDir: "/var/log/cgrates/cdre",
}
fltrCombiMed, _ := utils.NewRSRField("~mediation_runid:s/DEFAULT/SECOND_RUN/")
eCdreCfg.HeaderFields = []*CdreCdrField{
&CdreCdrField{
Name: "TypeOfRecord",
Type: "constant",
Value: "10",
Width: 2,
valueAsRsrField: &utils.RSRField{Id: "10"}},
&CdreCdrField{
Name: "LastCdr",
Type: "metatag",
Value: "last_cdr_time",
Layout: "020106150400",
Width: 12,
valueAsRsrField: &utils.RSRField{Id: "last_cdr_time"}},
}
eCdreCfg.ContentFields = []*CdreCdrField{
&CdreCdrField{
Name: "OperatorCode",
Type: "cdrfield",
Value: "operator",
Width: 2,
valueAsRsrField: &utils.RSRField{Id: "operator"},
fltrCombiMed, _ := utils.ParseRSRFields("~mediation_runid:s/DEFAULT/SECOND_RUN/", utils.INFIELD_SEP)
torVal, _ := utils.ParseRSRFields("^10", utils.INFIELD_SEP)
lastCdrVal, _ := utils.ParseRSRFields("^last_cdr_time", utils.INFIELD_SEP)
eCdreCfg.HeaderFields = []*CfgCdrField{
&CfgCdrField{
Tag: "TypeOfRecord",
Type: "constant",
Value: torVal,
Width: 2,
},
&CdreCdrField{
Name: "ProductId",
Type: "cdrfield",
Value: "productid",
Width: 5,
valueAsRsrField: &utils.RSRField{Id: "productid"},
},
&CdreCdrField{
Name: "NetworkId",
Type: "constant",
Value: "3",
Width: 1,
valueAsRsrField: &utils.RSRField{Id: "3"},
},
&CdreCdrField{
Name: "FromHttpPost1",
Type: "http_post",
Value: "https://localhost:8000",
Width: 10,
Strip: "xright",
Padding: "left",
valueAsRsrField: &utils.RSRField{Id: "https://localhost:8000"},
},
&CdreCdrField{
Name: "CombiMed1",
Type: "combimed",
Value: "cost",
Width: 10,
Strip: "xright",
Padding: "left",
Filter: fltrCombiMed,
valueAsRsrField: &utils.RSRField{Id: "cost"},
&CfgCdrField{
Tag: "LastCdr",
Type: "metatag",
CdrFieldId: "last_cdr_time",
Value: lastCdrVal,
Layout: "020106150400",
Strip: "xright",
Padding: "left",
Width: 12,
},
}
eCdreCfg.TrailerFields = []*CdreCdrField{
&CdreCdrField{
Name: "DistributorCode",
Type: "constant",
Value: "VOI",
Width: 3,
valueAsRsrField: &utils.RSRField{Id: "VOI"},
networkIdVal, _ := utils.ParseRSRFields("^3", utils.INFIELD_SEP)
fromHttpPost1Val, _ := utils.ParseRSRFields("^https://localhost:8000", utils.INFIELD_SEP)
eCdreCfg.ContentFields = []*CfgCdrField{
&CfgCdrField{
Tag: "OperatorCode",
Type: "cdrfield",
CdrFieldId: "operator",
Value: []*utils.RSRField{
&utils.RSRField{Id: "operator"}},
Width: 2,
Strip: "xright",
Padding: "left",
},
&CdreCdrField{
Name: "FileSeqNr",
Type: "metatag",
Value: "export_id",
Width: 5,
Padding: "zeroleft",
valueAsRsrField: &utils.RSRField{Id: "export_id"},
&CfgCdrField{
Tag: "ProductId",
Type: "cdrfield",
CdrFieldId: "productid",
Value: []*utils.RSRField{
&utils.RSRField{Id: "productid"}},
Width: 5,
Strip: "xright",
Padding: "left",
},
&CfgCdrField{
Tag: "NetworkId",
Type: "constant",
Value: networkIdVal,
Width: 1,
},
&CfgCdrField{
Tag: "FromHttpPost1",
Type: "http_post",
Value: fromHttpPost1Val,
Width: 10,
Strip: "xright",
Padding: "left",
},
&CfgCdrField{
Tag: "CombiMed1",
Type: "combimed",
CdrFieldId: "cost",
Value: []*utils.RSRField{
&utils.RSRField{Id: "cost"}},
Width: 10,
Strip: "xright",
Padding: "left",
Filter: fltrCombiMed,
Mandatory: true,
},
}
if rcvCdreCfg := xmlCdreCfgs["CDRE-FW2"].AsCdreConfig(); !reflect.DeepEqual(rcvCdreCfg, eCdreCfg) {
for _, fld := range rcvCdreCfg.ContentFields {
fmt.Printf("Fld: %+v\n", fld)
}
distribCodeVal, _ := utils.ParseRSRFields("^VOI", utils.INFIELD_SEP)
fileSeqNrVal, _ := utils.ParseRSRFields("^export_id", utils.INFIELD_SEP)
eCdreCfg.TrailerFields = []*CfgCdrField{
&CfgCdrField{
Tag: "DistributorCode",
Type: "constant",
Value: distribCodeVal,
Width: 3,
},
&CfgCdrField{
Tag: "FileSeqNr",
Type: "metatag",
CdrFieldId: "export_id",
Value: fileSeqNrVal,
Width: 5,
Strip: "xright",
Padding: "zeroleft",
},
}
if rcvCdreCfg, err := NewCdreConfigFromXmlCdreCfg(xmlCdreCfgs["CDRE-FW2"]); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rcvCdreCfg, eCdreCfg) {
t.Errorf("Expecting: %v, received: %v", eCdreCfg, rcvCdreCfg)
}
}

View File

@@ -38,6 +38,68 @@ func ParseCgrXmlConfig(reader io.Reader) (*CgrXmlCfgDocument, error) {
return xmlConfig, nil
}
// XML CDR field, used for both cdrc and cdre
type XmlCfgCdrField struct {
XMLName xml.Name `xml:"field"`
Tag *string `xml:"tag,attr"`
Type *string `xml:"type,attr"`
CdrFieldId *string `xml:"cdr_field,attr"`
Value *string `xml:"value,attr"`
Width *int `xml:"width,attr"` // Field width
Strip *string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright>
Padding *string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right>
Layout *string `xml:"layout,attr"` // Eg. time format layout
Filter *string `xml:"filter,attr"` // Eg. combimed filters
Mandatory *bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported
}
// One CDRC Configuration instance
type CgrXmlCdrcCfg struct {
Enabled *bool `xml:"enabled"` // Enable/Disable the
CdrsAddress *string `xml:"cdrs_address"` // The address where CDRs can be reached
CdrType *string `xml:"cdr_type"` // The type of CDR to process <csv>
FieldSeparator *string `xml:"field_separator"` // The separator to use when reading csvs
RunDelay *int64 `xml:"run_delay"` // Delay between runs
CdrInDir *string `xml:"cdr_in_dir"` // Folder to process CDRs from
CdrOutDir *string `xml:"cdr_out_dir"` // Folder to move processed CDRs to
CdrSourceId *string `xml:"cdr_source_id"` // Source identifier for the processed CDRs
CdrFields []*XmlCfgCdrField `xml:"fields>field"`
}
// The CdrExporter configuration instance
type CgrXmlCdreCfg struct {
CdrFormat *string `xml:"cdr_format"`
FieldSeparator *string `xml:"field_separator"`
DataUsageMultiplyFactor *float64 `xml:"data_usage_multiply_factor"`
CostMultiplyFactor *float64 `xml:"cost_multiply_factor"`
CostRoundingDecimals *int `xml:"cost_rounding_decimals"`
CostShiftDigits *int `xml:"cost_shift_digits"`
MaskDestId *string `xml:"mask_destination_id"`
MaskLength *int `xml:"mask_length"`
ExportDir *string `xml:"export_dir"`
Header *CgrXmlCfgCdrHeader `xml:"export_template>header"`
Content *CgrXmlCfgCdrContent `xml:"export_template>content"`
Trailer *CgrXmlCfgCdrTrailer `xml:"export_template>trailer"`
}
// CDR header
type CgrXmlCfgCdrHeader struct {
XMLName xml.Name `xml:"header"`
Fields []*XmlCfgCdrField `xml:"fields>field"`
}
// CDR content
type CgrXmlCfgCdrContent struct {
XMLName xml.Name `xml:"content"`
Fields []*XmlCfgCdrField `xml:"fields>field"`
}
// CDR trailer
type CgrXmlCfgCdrTrailer struct {
XMLName xml.Name `xml:"trailer"`
Fields []*XmlCfgCdrField `xml:"fields>field"`
}
// Define a format for configuration file, one doc contains more configuration instances, identified by section, type and id
type CgrXmlCfgDocument struct {
XMLName xml.Name `xml:"document"`
@@ -83,13 +145,6 @@ func (xmlCfg *CgrXmlCfgDocument) cacheCdrcCfgs() error {
} else if cdrcCfg == nil {
return fmt.Errorf("Could not unmarshal config instance: %s", cfgInst.Id)
}
// Cache rsr fields
for _, fld := range cdrcCfg.CdrFields {
if err := fld.PopulateRSRFields(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Id, err.Error())
}
}
cdrcCfg.setDefaults()
xmlCfg.cdrcs[cfgInst.Id] = cdrcCfg
}
return nil
@@ -108,39 +163,6 @@ func (xmlCfg *CgrXmlCfgDocument) cacheCdreCfgs() error {
} else if cdreCfg == nil {
return fmt.Errorf("Could not unmarshal CgrXmlCdreCfg: %s", cfgInst.Id)
}
if cdreCfg.Header != nil {
// Cache rsr fields
for _, fld := range cdreCfg.Header.Fields {
if err := fld.populateRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
if err := fld.populateFltrRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
}
}
if cdreCfg.Content != nil {
// Cache rsr fields
for _, fld := range cdreCfg.Content.Fields {
if err := fld.populateRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
if err := fld.populateFltrRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
}
}
if cdreCfg.Trailer != nil {
// Cache rsr fields
for _, fld := range cdreCfg.Trailer.Fields {
if err := fld.populateRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
if err := fld.populateFltrRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
}
}
xmlCfg.cdres[cfgInst.Id] = cdreCfg
}
return nil

View File

@@ -74,7 +74,7 @@ func TestSearchExtraFieldLast(t *testing.T) {
func TestSearchExtraField(t *testing.T) {
fsCdr, _ := NewFSCdr(body)
rsrSt1, _ := utils.NewRSRField("^injected_value")
rsrSt2, _ := utils.NewRSRField("^injected_hdr/injected_value/")
rsrSt2, _ := utils.NewRSRField("^injected_hdr::injected_value/")
cfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "caller_id_name"}, rsrSt1, rsrSt2}
extraFields := fsCdr.getExtraFields()
if len(extraFields) != 3 || extraFields["caller_id_name"] != "dan" ||

View File

@@ -96,9 +96,9 @@ func TestCreateCdrDirs(t *testing.T) {
if !*testLocal {
return
}
for _, cdrcDir := range []string{cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir,
cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV2")["CDRC-CSV2"].CdrInDir, cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV2")["CDRC-CSV2"].CdrOutDir,
cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV3")["CDRC-CSV3"].CdrInDir, cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV3")["CDRC-CSV3"].CdrOutDir} {
for _, cdrcDir := range []string{cfg.CdrcInstances[0].CdrInDir, cfg.CdrcInstances[0].CdrOutDir,
*cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV2")["CDRC-CSV2"].CdrInDir, *cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV2")["CDRC-CSV2"].CdrOutDir,
*cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV3")["CDRC-CSV3"].CdrInDir, *cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV3")["CDRC-CSV3"].CdrOutDir} {
if err := os.RemoveAll(cdrcDir); err != nil {
t.Fatal("Error removing folder: ", cdrcDir, err)
}
@@ -151,7 +151,7 @@ dbafe9c8614c785a65aabd116dd3959c3c56f7f7,default,*voice,dsafdsag,rated,*out,cgra
if err := ioutil.WriteFile(tmpFilePath, []byte(fileContent1), 0644); err != nil {
t.Fatal(err.Error)
}
if err := os.Rename(tmpFilePath, path.Join(cfg.CdrcCdrInDir, fileName)); err != nil {
if err := os.Rename(tmpFilePath, path.Join(cfg.CdrcInstances[0].CdrInDir, fileName)); err != nil {
t.Fatal("Error moving file to processing directory: ", err)
}
}
@@ -169,7 +169,7 @@ func TestHandleCdr2File(t *testing.T) {
if err := ioutil.WriteFile(tmpFilePath, []byte(fileContent), 0644); err != nil {
t.Fatal(err.Error)
}
if err := os.Rename(tmpFilePath, path.Join(cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV2")["CDRC-CSV2"].CdrInDir, fileName)); err != nil {
if err := os.Rename(tmpFilePath, path.Join(*cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV2")["CDRC-CSV2"].CdrInDir, fileName)); err != nil {
t.Fatal("Error moving file to processing directory: ", err)
}
}
@@ -186,7 +186,7 @@ func TestHandleCdr3File(t *testing.T) {
if err := ioutil.WriteFile(tmpFilePath, []byte(fileContent), 0644); err != nil {
t.Fatal(err.Error)
}
if err := os.Rename(tmpFilePath, path.Join(cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV3")["CDRC-CSV3"].CdrInDir, fileName)); err != nil {
if err := os.Rename(tmpFilePath, path.Join(*cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV3")["CDRC-CSV3"].CdrInDir, fileName)); err != nil {
t.Fatal("Error moving file to processing directory: ", err)
}
}

View File

@@ -91,6 +91,7 @@ func TestTutLclStartEngine(t *testing.T) {
t.Fatal("Cannot find cgr-engine executable")
}
exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
engine := exec.Command(enginePath, "-config", tutCfgPath)
if err := engine.Start(); err != nil {
t.Fatal(err)

View File

@@ -76,6 +76,7 @@ const (
FALLBACK_SEP = ';'
INFIELD_SEP = ";"
FIELDS_SEP = ","
STATIC_HDRVAL_SEP = "::"
REGEXP_PREFIX = "~"
JSON = "json"
GOB = "gob"
@@ -105,6 +106,7 @@ const (
STATIC_VALUE_PREFIX = "^"
CSV = "csv"
CDRE_DRYRUN = "dry_run"
COMBIMED = "combimed"
INTERNAL = "internal"
ZERO_RATING_SUBJECT_PREFIX = "*zero"
OK = "OK"
@@ -159,6 +161,10 @@ const (
CREATE_TARIFFPLAN_TABLES_SQL = "create_tariffplan_tables.sql"
TEST_SQL = "TEST_SQL"
EMPTY = "_empty_"
CONSTANT = "constant"
FILLER = "filler"
METATAG = "metatag"
HTTP_POST = "http_post"
)
var (

View File

@@ -315,3 +315,12 @@ func Unzip(src, dest string) error {
return nil
}
// Utilities to provide pointers where we need to define ad-hoc
func StringPointer(str string) *string {
return &str
}
func IntPointer(i int) *int {
return &i
}

View File

@@ -30,12 +30,12 @@ func NewRSRField(fldStr string) (*RSRField, error) {
}
if strings.HasPrefix(fldStr, STATIC_VALUE_PREFIX) { // Special case when RSR is defined as static header/value
var staticHdr, staticVal string
if splt := strings.Split(fldStr, "/"); len(splt) == 3 { // Using / as separator since ':' is often use in date/time fields
if len(splt[2]) != 0 { // Last split has created empty element
return nil, fmt.Errorf("Invalid static header/value combination: %s", fldStr)
}
if splt := strings.Split(fldStr, STATIC_HDRVAL_SEP); len(splt) == 2 { // Using / as separator since ':' is often use in date/time fields
staticHdr, staticVal = splt[0][1:], splt[1] // Strip the / suffix
} else if len(splt) == 2 {
if strings.HasSuffix(staticVal, "/") { // If value ends with /, strip it since it is a part of the definition syntax
staticVal = staticVal[:len(staticVal)-1]
}
} else if len(splt) > 2 {
return nil, fmt.Errorf("Invalid RSRField string: %s", fldStr)
} else {
staticHdr, staticVal = splt[0][1:], splt[0][1:] // If no split, header will remain as original, value as header without the prefix
@@ -102,13 +102,13 @@ func (rsrf *RSRField) RegexpMatched() bool { // Investigate whether we had a reg
}
// Parses list of RSRFields, used for example as multiple filters in derived charging
func ParseRSRFields(fldsStr, sep string) ([]*RSRField, error) {
func ParseRSRFields(fldsStr, sep string) (RSRFields, error) {
//rsrRlsPattern := regexp.MustCompile(`^(~\w+:s/.+/.*/)|(\^.+(/.+/)?)(;(~\w+:s/.+/.*/)|(\^.+(/.+/)?))*$`) //ToDo:Fix here rule able to confirm the content
if len(fldsStr) == 0 {
return nil, nil
}
rulesSplt := strings.Split(fldsStr, sep)
rsrFields := make([]*RSRField, len(rulesSplt))
rsrFields := make(RSRFields, len(rulesSplt))
for idx, ruleStr := range rulesSplt {
if rsrField, err := NewRSRField(ruleStr); err != nil {
return nil, err
@@ -118,3 +118,5 @@ func ParseRSRFields(fldsStr, sep string) ([]*RSRField, error) {
}
return rsrFields, nil
}
type RSRFields []*RSRField

View File

@@ -89,7 +89,7 @@ func TestConvertPlusNationalAnd00(t *testing.T) {
}
func TestRSRParseStatic(t *testing.T) {
if rsrField, err := NewRSRField("^static_header/static_value/"); err != nil {
if rsrField, err := NewRSRField("^static_header::static_value/"); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rsrField, &RSRField{Id: "static_header", staticValue: "static_value"}) {
t.Errorf("Unexpected RSRField received: %v", rsrField)
@@ -150,14 +150,14 @@ func TestParseRSRFields(t *testing.T) {
rsrFld2, _ := NewRSRField(`~subject:s/^0\d{9}$//`)
rsrFld3, _ := NewRSRField(`^destination/+4912345/`)
rsrFld4, _ := NewRSRField(`~mediation_runid:s/^default$/default/`)
eRSRFields := []*RSRField{rsrFld1, rsrFld2, rsrFld3, rsrFld4}
eRSRFields := RSRFields{rsrFld1, rsrFld2, rsrFld3, rsrFld4}
if rsrFlds, err := ParseRSRFields(fieldsStr1, INFIELD_SEP); err != nil {
t.Error("Unexpected error: ", err)
} else if !reflect.DeepEqual(eRSRFields, rsrFlds) {
t.Errorf("Expecting: %v, received: %v", eRSRFields, rsrFlds)
}
fields := `host,~sip_redirected_to:s/sip:\+49(\d+)@/0$1/,destination`
expectParsedFields := []*RSRField{
expectParsedFields := RSRFields{
&RSRField{Id: "host"},
&RSRField{Id: "sip_redirected_to",
RSRules: []*ReSearchReplace{&ReSearchReplace{SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`), ReplaceTemplate: "0$1"}}},

View File

@@ -112,7 +112,7 @@ func TestPassesFieldFilter(t *testing.T) {
if pass, _ := cdr.PassesFieldFilter(acntPrefxFltr); pass {
t.Error("Passing filter")
}
torFltr, _ := NewRSRField(`^tor/*voice/`)
torFltr, _ := NewRSRField(`^tor::*voice/`)
if pass, _ := cdr.PassesFieldFilter(torFltr); !pass {
t.Error("Not passing filter")
}