mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Refactoring CDRE and CDRC configurations and functionality
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
67
cdrc/cdrc.go
67
cdrc/cdrc.go
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
136
config/config.go
136
config/config.go
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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/"MatchedDestId":".+_(\s\s\s\s\s)"/$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/"MatchedDestId":".+_(\s\s\s\s\s)"/$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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" ||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}}},
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user