mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Refactoring CDRs to support RSRFields
This commit is contained in:
@@ -1422,9 +1422,9 @@ func TestLocalSetDC(t *testing.T) {
|
||||
return
|
||||
}
|
||||
dcs1 := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", TorField: "*default",
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", TorField: "*default",
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
|
||||
}
|
||||
attrs := AttrSetDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "dan", Subject: "dan", DerivedChargers: dcs1}
|
||||
@@ -1442,9 +1442,9 @@ func TestLocalGetDC(t *testing.T) {
|
||||
}
|
||||
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
|
||||
eDcs := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", TorField: "*default",
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", TorField: "*default",
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
|
||||
}
|
||||
var dcs utils.DerivedChargers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
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
|
||||
@@ -85,9 +85,9 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
|
||||
exportedIds[idxCdr] = cdr.CgrId
|
||||
}
|
||||
*reply = utils.ExportedFileCdrs{ExportedFilePath: utils.CDRE_DRYRUN, TotalRecords: len(cdrs), ExportedCgrIds: exportedIds}
|
||||
case utils.CDRE_CSV:
|
||||
case utils.CSV:
|
||||
if len(exportDir) == 0 {
|
||||
exportDir = path.Join(self.Config.CdreDir, utils.CDRE_CSV)
|
||||
exportDir = path.Join(self.Config.CdreDir, utils.CSV)
|
||||
}
|
||||
if len(fileName) == 0 {
|
||||
fileName = fmt.Sprintf("cdre_%s.csv", exportId)
|
||||
|
||||
@@ -47,9 +47,9 @@ func TestGetEmptyDC(t *testing.T) {
|
||||
|
||||
func TestSetDC(t *testing.T) {
|
||||
dcs1 := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", TorField: "*default",
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", TorField: "*default",
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
|
||||
}
|
||||
attrs := AttrSetDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "dan", Subject: "dan", DerivedChargers: dcs1}
|
||||
@@ -64,9 +64,9 @@ func TestSetDC(t *testing.T) {
|
||||
func TestGetDC(t *testing.T) {
|
||||
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
|
||||
eDcs := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", TorField: "*default",
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", TorField: "*default",
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
|
||||
}
|
||||
var dcs utils.DerivedChargers
|
||||
|
||||
94
cdrc/cdrc.go
94
cdrc/cdrc.go
@@ -21,7 +21,6 @@ package cdrc
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -29,7 +28,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/cdrs"
|
||||
@@ -52,9 +50,6 @@ func NewCdrc(config *config.CGRConfig, cdrServer *cdrs.CDRS) (*Cdrc, error) {
|
||||
return nil, fmt.Errorf("Folder %s does not exist", dir)
|
||||
}
|
||||
}
|
||||
if err := cdrc.parseFieldsConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cdrc.httpClient = new(http.Client)
|
||||
return cdrc, nil
|
||||
}
|
||||
@@ -78,100 +73,57 @@ func (self *Cdrc) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Loads all fields (primary and extra) into cfgCdrFields, do some pre-checks (eg: in case of csv make sure that values are integers)
|
||||
func (self *Cdrc) parseFieldsConfig() error {
|
||||
var err error
|
||||
self.cfgCdrFields = map[string]string{
|
||||
utils.ACCID: self.cgrCfg.CdrcAccIdField,
|
||||
utils.REQTYPE: self.cgrCfg.CdrcReqTypeField,
|
||||
utils.DIRECTION: self.cgrCfg.CdrcDirectionField,
|
||||
utils.TENANT: self.cgrCfg.CdrcTenantField,
|
||||
utils.Category: self.cgrCfg.CdrcCategoryField,
|
||||
utils.ACCOUNT: self.cgrCfg.CdrcAccountField,
|
||||
utils.SUBJECT: self.cgrCfg.CdrcSubjectField,
|
||||
utils.DESTINATION: self.cgrCfg.CdrcDestinationField,
|
||||
utils.SETUP_TIME: self.cgrCfg.CdrcSetupTimeField,
|
||||
utils.ANSWER_TIME: self.cgrCfg.CdrcAnswerTimeField,
|
||||
utils.DURATION: self.cgrCfg.CdrcDurationField,
|
||||
}
|
||||
|
||||
// Add extra fields here, config extra fields in the form of []string{"fieldName1:indxInCsv1","fieldName2: indexInCsv2"}
|
||||
for _, fieldWithIdx := range self.cgrCfg.CdrcExtraFields {
|
||||
splt := strings.Split(fieldWithIdx, ":")
|
||||
if len(splt) != 2 {
|
||||
return errors.New("Cannot parse cdrc.extra_fields")
|
||||
}
|
||||
if utils.IsSliceMember(utils.PrimaryCdrFields, splt[0]) {
|
||||
return errors.New("Extra cdrc.extra_fields overwriting primary fields")
|
||||
}
|
||||
self.cfgCdrFields[splt[0]] = splt[1]
|
||||
}
|
||||
// Fields populated, do some sanity checks here
|
||||
for cdrField, cfgVal := range self.cfgCdrFields {
|
||||
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cgrCfg.CdrcCdrType) && !strings.HasPrefix(cfgVal, utils.STATIC_VALUE_PREFIX) {
|
||||
if _, err = strconv.Atoi(cfgVal); err != nil {
|
||||
return fmt.Errorf("Cannot parse configuration field %s into integer", cdrField)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Takes the record out of csv and turns it into http form which can be posted
|
||||
func (self *Cdrc) recordForkCdr(record []string) (*utils.StoredCdr, error) {
|
||||
ratedCdr := &utils.StoredCdr{CdrSource: self.cgrCfg.CdrcSourceId, ExtraFields: map[string]string{}, Cost: -1}
|
||||
storedCdr := &utils.StoredCdr{CdrSource: self.cgrCfg.CdrcSourceId, ExtraFields: map[string]string{}, Cost: -1}
|
||||
var err error
|
||||
for cfgFieldName, cfgFieldVal := range self.cfgCdrFields {
|
||||
for cfgFieldName, cfgFieldRSR := range self.cgrCfg.CdrcCdrFields {
|
||||
var fieldVal string
|
||||
if strings.HasPrefix(cfgFieldVal, utils.STATIC_VALUE_PREFIX) {
|
||||
fieldVal = cfgFieldVal[1:]
|
||||
} else if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cgrCfg.CdrcCdrType) {
|
||||
if cfgFieldIdx, err := strconv.Atoi(cfgFieldVal); err != nil { // Should in theory never happen since we have already parsed config
|
||||
return nil, err
|
||||
} else if len(record) <= cfgFieldIdx {
|
||||
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cgrCfg.CdrcCdrType) {
|
||||
if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx {
|
||||
return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName)
|
||||
} else {
|
||||
fieldVal = record[cfgFieldIdx]
|
||||
fieldVal = cfgFieldRSR.ParseValue(record[cfgFieldIdx])
|
||||
}
|
||||
} else { // Modify here when we add more supported cdr formats
|
||||
fieldVal = "UNKNOWN"
|
||||
}
|
||||
switch cfgFieldName {
|
||||
case utils.ACCID:
|
||||
ratedCdr.AccId = fieldVal
|
||||
storedCdr.AccId = fieldVal
|
||||
case utils.REQTYPE:
|
||||
ratedCdr.ReqType = fieldVal
|
||||
storedCdr.ReqType = fieldVal
|
||||
case utils.DIRECTION:
|
||||
ratedCdr.Direction = fieldVal
|
||||
storedCdr.Direction = fieldVal
|
||||
case utils.TENANT:
|
||||
ratedCdr.Tenant = fieldVal
|
||||
case utils.Category:
|
||||
ratedCdr.Category = fieldVal
|
||||
storedCdr.Tenant = fieldVal
|
||||
case utils.CATEGORY:
|
||||
storedCdr.Category = fieldVal
|
||||
case utils.ACCOUNT:
|
||||
ratedCdr.Account = fieldVal
|
||||
storedCdr.Account = fieldVal
|
||||
case utils.SUBJECT:
|
||||
ratedCdr.Subject = fieldVal
|
||||
storedCdr.Subject = fieldVal
|
||||
case utils.DESTINATION:
|
||||
ratedCdr.Destination = fieldVal
|
||||
storedCdr.Destination = fieldVal
|
||||
case utils.SETUP_TIME:
|
||||
if ratedCdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
|
||||
if storedCdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse answer time field, err: %s", err.Error())
|
||||
}
|
||||
case utils.ANSWER_TIME:
|
||||
if ratedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
|
||||
if storedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse answer time field, err: %s", err.Error())
|
||||
}
|
||||
case utils.DURATION:
|
||||
if ratedCdr.Duration, err = utils.ParseDurationWithSecs(fieldVal); err != nil {
|
||||
if storedCdr.Duration, err = utils.ParseDurationWithSecs(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse duration field, err: %s", err.Error())
|
||||
}
|
||||
default: // Extra fields will not match predefined so they all show up here
|
||||
ratedCdr.ExtraFields[cfgFieldName] = fieldVal
|
||||
storedCdr.ExtraFields[cfgFieldName] = fieldVal
|
||||
}
|
||||
|
||||
}
|
||||
ratedCdr.CgrId = utils.Sha1(ratedCdr.AccId, ratedCdr.SetupTime.String())
|
||||
return ratedCdr, nil
|
||||
storedCdr.CgrId = utils.Sha1(storedCdr.AccId, storedCdr.SetupTime.String())
|
||||
return storedCdr, nil
|
||||
}
|
||||
|
||||
// One run over the CDR folder
|
||||
@@ -233,18 +185,18 @@ func (self *Cdrc) processFile(filePath string) error {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Error in csv file: %s", err.Error()))
|
||||
continue // Other csv related errors, ignore
|
||||
}
|
||||
rawCdr, err := self.recordForkCdr(record)
|
||||
storedCdr, err := self.recordForkCdr(record)
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Error in csv file: %s", err.Error()))
|
||||
continue
|
||||
}
|
||||
if self.cgrCfg.CdrcCdrs == utils.INTERNAL {
|
||||
if err := self.cdrServer.ProcessRawCdr(rawCdr); err != nil {
|
||||
if err := self.cdrServer.ProcessRawCdr(storedCdr); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, error: %s", err.Error()))
|
||||
continue
|
||||
}
|
||||
} else { // CDRs listening on IP
|
||||
if _, err := self.httpClient.PostForm(fmt.Sprintf("http://%s/cgr", self.cgrCfg.HTTPListen), rawCdr.AsRawCdrHttpForm()); err != nil {
|
||||
if _, err := self.httpClient.PostForm(fmt.Sprintf("http://%s/cgr", self.cgrCfg.HTTPListen), storedCdr.AsHttpForm()); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, error: %s", err.Error()))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -26,43 +26,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParseFieldsConfig(t *testing.T) {
|
||||
// Test default config
|
||||
cgrConfig, _ := config.NewDefaultCGRConfig()
|
||||
// Test primary field index definition
|
||||
cgrConfig.CdrcAccIdField = "detect_me"
|
||||
cdrc := &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err == nil {
|
||||
t.Error("Failed detecting error in accounting id definition", err)
|
||||
}
|
||||
cgrConfig.CdrcAccIdField = "^static_val"
|
||||
cgrConfig.CdrcSubjectField = "1"
|
||||
cdrc = &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err != nil {
|
||||
t.Error("Failed to corectly parse primary fields %v", cdrc.cfgCdrFields)
|
||||
}
|
||||
cgrConfig.CdrcExtraFields = []string{"^static_val:orig_ip"}
|
||||
// Test extra field index definition
|
||||
cgrConfig.CdrcAccIdField = "0" // Put back as int
|
||||
cgrConfig.CdrcExtraFields = []string{"supplier1", "orig_ip:11"}
|
||||
cdrc = &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err == nil {
|
||||
t.Error("Failed detecting error in extra fields definition", err)
|
||||
}
|
||||
cgrConfig.CdrcExtraFields = []string{"supplier1:^top_supplier", "orig_ip:11"}
|
||||
cdrc = &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err != nil {
|
||||
t.Errorf("Failed to corectly parse extra fields %v", cdrc.cfgCdrFields)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordForkCdr(t *testing.T) {
|
||||
cgrConfig, _ := config.NewDefaultCGRConfig()
|
||||
cgrConfig.CdrcExtraFields = []string{"supplier:11"}
|
||||
cgrConfig.CdrcCdrFields["supplier"] = &utils.RSRField{Id: "11"}
|
||||
cdrc := &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err != nil {
|
||||
t.Error("Failed parsing default fieldIndexesFromConfig", err)
|
||||
}
|
||||
cdrRow := []string{"firstField", "secondField"}
|
||||
_, err := cdrc.recordForkCdr(cdrRow)
|
||||
if err == nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
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
|
||||
@@ -59,12 +59,12 @@ func (csvwr *CsvCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
if fld.Id == utils.COST {
|
||||
fldVal = cdr.FormatCost(csvwr.costShiftDigits, csvwr.roundDecimals)
|
||||
} else if fld.Id == utils.DESTINATION {
|
||||
fldVal = cdr.ExportFieldValue(utils.DESTINATION)
|
||||
fldVal = cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION})
|
||||
if len(csvwr.maskDestId) != 0 && csvwr.maskLen > 0 && engine.CachedDestHasPrefix(csvwr.maskDestId, fldVal) {
|
||||
fldVal = MaskDestination(fldVal, csvwr.maskLen)
|
||||
}
|
||||
} else {
|
||||
fldVal = cdr.ExportFieldValue(fld.Id)
|
||||
fldVal = cdr.FieldAsString(fld)
|
||||
}
|
||||
row[idx] = fld.ParseValue(fldVal)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
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
|
||||
@@ -125,12 +125,12 @@ func (fwv *FixedWidthCdrWriter) cdrFieldValue(cdr *utils.StoredCdr, cfgHdr, layo
|
||||
case utils.ANSWER_TIME: // Format time based on layout
|
||||
cdrVal = cdr.AnswerTime.Format(layout)
|
||||
case utils.DESTINATION:
|
||||
cdrVal = cdr.ExportFieldValue(utils.DESTINATION)
|
||||
cdrVal = cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION})
|
||||
if fwv.maskLen != -1 && fwv.maskedDestination(cdrVal) {
|
||||
cdrVal = MaskDestination(cdrVal, fwv.maskLen)
|
||||
}
|
||||
default:
|
||||
cdrVal = cdr.ExportFieldValue(rsrField.Id)
|
||||
cdrVal = cdr.FieldAsString(rsrField)
|
||||
}
|
||||
return rsrField.ParseValue(cdrVal), nil
|
||||
}
|
||||
@@ -273,7 +273,7 @@ func (fwv *FixedWidthCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
}
|
||||
case METATAG:
|
||||
if cfgFld.Value == META_MASKDESTINATION {
|
||||
outVal, err = fwv.metaHandler(cfgFld.Value, cdr.ExportFieldValue(utils.DESTINATION))
|
||||
outVal, err = fwv.metaHandler(cfgFld.Value, cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION}))
|
||||
} else {
|
||||
outVal, err = fwv.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
}
|
||||
|
||||
16
cdrs/cdrs.go
16
cdrs/cdrs.go
@@ -36,13 +36,13 @@ var (
|
||||
)
|
||||
|
||||
// Returns error if not able to properly store the CDR, mediation is async since we can always recover offline
|
||||
func storeAndMediate(rawCdr utils.RawCDR) error {
|
||||
if err := storage.SetCdr(rawCdr); err != nil {
|
||||
func storeAndMediate(storedCdr *utils.StoredCdr) error {
|
||||
if err := storage.SetCdr(storedCdr); err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.CDRSMediator == utils.INTERNAL {
|
||||
go func() {
|
||||
if err := medi.RateCdr(rawCdr); err != nil {
|
||||
if err := medi.RateCdr(storedCdr); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("Could not run mediation on CDR: %s", err.Error()))
|
||||
}
|
||||
}()
|
||||
@@ -56,7 +56,7 @@ func cgrCdrHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error()))
|
||||
}
|
||||
if err := storeAndMediate(cgrCdr); err != nil {
|
||||
if err := storeAndMediate(cgrCdr.AsStoredCdr()); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
@@ -64,11 +64,11 @@ func cgrCdrHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Handler for fs http
|
||||
func fsCdrHandler(w http.ResponseWriter, r *http.Request) {
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
fsCdr, err := new(FSCdr).New(body)
|
||||
fsCdr, err := NewFSCdr(body)
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error()))
|
||||
}
|
||||
if err := storeAndMediate(fsCdr); err != nil {
|
||||
if err := storeAndMediate(fsCdr.AsStoredCdr()); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,6 @@ func (cdrs *CDRS) RegisterHanlersToServer(server *engine.Server) {
|
||||
}
|
||||
|
||||
// Used to internally process CDR
|
||||
func (cdrs *CDRS) ProcessRawCdr(rawCdr utils.RawCDR) error {
|
||||
return storeAndMediate(rawCdr)
|
||||
func (cdrs *CDRS) ProcessRawCdr(rawCdr utils.RawCdr) error {
|
||||
return storeAndMediate(rawCdr.AsStoredCdr())
|
||||
}
|
||||
|
||||
223
cdrs/fscdr.go
223
cdrs/fscdr.go
@@ -20,12 +20,9 @@ package cdrs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
@@ -54,12 +51,8 @@ const (
|
||||
FS_SIP_REQUSER = "sip_req_user" // Apps like FusionPBX do not set dialed_extension, alternative being destination_number but that comes in customer profile, not in vars
|
||||
)
|
||||
|
||||
type FSCdr struct {
|
||||
vars map[string]string
|
||||
body map[string]interface{} // keeps the loaded body for extra field search
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) New(body []byte) (utils.RawCDR, error) {
|
||||
func NewFSCdr(body []byte) (*FSCdr, error) {
|
||||
fsCdr := new(FSCdr)
|
||||
fsCdr.vars = make(map[string]string)
|
||||
var err error
|
||||
if err = json.Unmarshal(body, &fsCdr.body); err == nil {
|
||||
@@ -75,49 +68,23 @@ func (fsCdr FSCdr) New(body []byte) (utils.RawCDR, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) GetCgrId() string {
|
||||
setupTime, _ := fsCdr.GetSetupTime()
|
||||
type FSCdr struct {
|
||||
vars map[string]string
|
||||
body map[string]interface{} // keeps the loaded body for extra field search
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) getCgrId() string {
|
||||
setupTime, _ := utils.ParseTimeDetectLayout(fsCdr.vars[FS_SETUP_TIME])
|
||||
return utils.Sha1(fsCdr.vars[FS_UUID], setupTime.String())
|
||||
}
|
||||
func (fsCdr FSCdr) GetAccId() string {
|
||||
return fsCdr.vars[FS_UUID]
|
||||
}
|
||||
func (fsCdr FSCdr) GetCdrHost() string {
|
||||
return fsCdr.vars[FS_IP]
|
||||
}
|
||||
func (fsCdr FSCdr) GetCdrSource() string {
|
||||
return FS_CDR_SOURCE
|
||||
}
|
||||
func (fsCdr FSCdr) GetDirection() string {
|
||||
//TODO: implement direction, not related to FS_DIRECTION but traffic towards or from subject/account
|
||||
return "*out"
|
||||
}
|
||||
func (fsCdr FSCdr) GetSubject() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_SUBJECT], fsCdr.vars[FS_USERNAME])
|
||||
}
|
||||
func (fsCdr FSCdr) GetAccount() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_ACCOUNT], fsCdr.vars[FS_USERNAME])
|
||||
}
|
||||
|
||||
// Charging destination number
|
||||
func (fsCdr FSCdr) GetDestination() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_DESTINATION], fsCdr.vars[FS_CALL_DEST_NR], fsCdr.vars[FS_SIP_REQUSER])
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) GetCategory() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_CATEGORY], cfg.DefaultCategory)
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) GetTenant() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_CSTMID], cfg.DefaultTenant)
|
||||
}
|
||||
func (fsCdr FSCdr) GetReqType() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_REQTYPE], cfg.DefaultReqType)
|
||||
}
|
||||
func (fsCdr FSCdr) GetExtraFields() map[string]string {
|
||||
func (fsCdr FSCdr) getExtraFields() map[string]string {
|
||||
extraFields := make(map[string]string, len(cfg.CDRSExtraFields))
|
||||
for _, field := range cfg.CDRSExtraFields {
|
||||
origFieldVal, foundInVars := fsCdr.vars[field.Id]
|
||||
if strings.HasPrefix(field.Id, utils.STATIC_VALUE_PREFIX) { // Support for static values injected in the CDRS. it will show up as {^value:value}
|
||||
foundInVars = true
|
||||
}
|
||||
if !foundInVars {
|
||||
origFieldVal = fsCdr.searchExtraField(field.Id, fsCdr.body)
|
||||
}
|
||||
@@ -154,149 +121,23 @@ func (fsCdr FSCdr) searchExtraField(field string, body map[string]interface{}) (
|
||||
return
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) GetSetupTime() (t time.Time, err error) {
|
||||
return utils.ParseTimeDetectLayout(fsCdr.vars[FS_SETUP_TIME])
|
||||
}
|
||||
func (fsCdr FSCdr) GetAnswerTime() (t time.Time, err error) {
|
||||
return utils.ParseTimeDetectLayout(fsCdr.vars[FS_ANSWER_TIME])
|
||||
}
|
||||
func (fsCdr FSCdr) GetHangupTime() (t time.Time, err error) {
|
||||
return utils.ParseTimeDetectLayout(fsCdr.vars[FS_HANGUP_TIME])
|
||||
}
|
||||
|
||||
// Extracts duration as considered by the telecom switch
|
||||
func (fsCdr FSCdr) GetDuration() (time.Duration, error) {
|
||||
return utils.ParseDurationWithSecs(fsCdr.vars[FS_DURATION])
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) Store() (result string, err error) {
|
||||
result += fsCdr.GetCgrId() + "|"
|
||||
result += fsCdr.GetAccId() + "|"
|
||||
result += fsCdr.GetCdrHost() + "|"
|
||||
result += fsCdr.GetDirection() + "|"
|
||||
result += fsCdr.GetSubject() + "|"
|
||||
result += fsCdr.GetAccount() + "|"
|
||||
result += fsCdr.GetDestination() + "|"
|
||||
result += fsCdr.GetCategory() + "|"
|
||||
result += fsCdr.GetAccId() + "|"
|
||||
result += fsCdr.GetTenant() + "|"
|
||||
result += fsCdr.GetReqType() + "|"
|
||||
st, err := fsCdr.GetAnswerTime()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += strconv.FormatInt(st.UnixNano(), 10) + "|"
|
||||
et, err := fsCdr.GetHangupTime()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += strconv.FormatInt(et.UnixNano(), 10) + "|"
|
||||
dur, _ := fsCdr.GetDuration()
|
||||
result += strconv.FormatInt(int64(dur.Seconds()), 10) + "|"
|
||||
return
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) Restore(input string) error {
|
||||
return errors.New("Not implemented")
|
||||
}
|
||||
|
||||
// Used in extra mediation
|
||||
func (fsCdr FSCdr) ForkCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*utils.StoredCdr, error) {
|
||||
if utils.IsSliceMember([]string{runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld}, "") {
|
||||
return nil, errors.New(fmt.Sprintf("%s:FieldName", utils.ERR_MANDATORY_IE_MISSING)) // All input field names are mandatory
|
||||
}
|
||||
var err error
|
||||
var hasKey bool
|
||||
var sTimeStr, aTimeStr, durStr string
|
||||
rtCdr := new(utils.StoredCdr)
|
||||
rtCdr.MediationRunId = runId
|
||||
rtCdr.Cost = -1.0 // Default for non-rated CDR
|
||||
if rtCdr.AccId = fsCdr.GetAccId(); len(rtCdr.AccId) == 0 {
|
||||
if fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.ACCID))
|
||||
} else { // Not mandatory, need to generate here CgrId
|
||||
rtCdr.CgrId = utils.GenUUID()
|
||||
}
|
||||
}
|
||||
if rtCdr.CdrHost = fsCdr.GetCdrHost(); len(rtCdr.CdrHost) == 0 && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.CDRHOST))
|
||||
}
|
||||
if rtCdr.CdrSource = fsCdr.GetCdrSource(); len(rtCdr.CdrSource) == 0 && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.CDRSOURCE))
|
||||
}
|
||||
if strings.HasPrefix(reqTypeFld, utils.STATIC_VALUE_PREFIX) { // Values starting with prefix are not dynamically populated
|
||||
rtCdr.ReqType = reqTypeFld[1:]
|
||||
} else if rtCdr.ReqType, hasKey = fsCdr.vars[reqTypeFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, reqTypeFld))
|
||||
}
|
||||
if strings.HasPrefix(directionFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Direction = directionFld[1:]
|
||||
} else if rtCdr.Direction, hasKey = fsCdr.vars[directionFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, directionFld))
|
||||
}
|
||||
if strings.HasPrefix(tenantFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Tenant = tenantFld[1:]
|
||||
} else if rtCdr.Tenant, hasKey = fsCdr.vars[tenantFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, tenantFld))
|
||||
}
|
||||
if strings.HasPrefix(torFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Category = torFld[1:]
|
||||
} else if rtCdr.Category, hasKey = fsCdr.vars[torFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, torFld))
|
||||
}
|
||||
if strings.HasPrefix(accountFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Account = accountFld[1:]
|
||||
} else if rtCdr.Account, hasKey = fsCdr.vars[accountFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, accountFld))
|
||||
}
|
||||
if strings.HasPrefix(subjectFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Subject = subjectFld[1:]
|
||||
} else if rtCdr.Subject, hasKey = fsCdr.vars[subjectFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, subjectFld))
|
||||
}
|
||||
if strings.HasPrefix(destFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Destination = destFld[1:]
|
||||
} else if rtCdr.Destination, hasKey = fsCdr.vars[destFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, destFld))
|
||||
}
|
||||
if sTimeStr, hasKey = fsCdr.vars[setupTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(setupTimeFld, utils.STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, setupTimeFld))
|
||||
} else {
|
||||
if strings.HasPrefix(setupTimeFld, utils.STATIC_VALUE_PREFIX) {
|
||||
sTimeStr = setupTimeFld[1:]
|
||||
}
|
||||
if rtCdr.SetupTime, err = utils.ParseTimeDetectLayout(sTimeStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if aTimeStr, hasKey = fsCdr.vars[answerTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(answerTimeFld, utils.STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, answerTimeFld))
|
||||
} else {
|
||||
if strings.HasPrefix(answerTimeFld, utils.STATIC_VALUE_PREFIX) {
|
||||
aTimeStr = answerTimeFld[1:]
|
||||
}
|
||||
if rtCdr.AnswerTime, err = utils.ParseTimeDetectLayout(aTimeStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if durStr, hasKey = fsCdr.vars[durationFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(durationFld, utils.STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, durationFld))
|
||||
} else {
|
||||
if strings.HasPrefix(durationFld, utils.STATIC_VALUE_PREFIX) {
|
||||
durStr = durationFld[1:]
|
||||
}
|
||||
if rtCdr.Duration, err = utils.ParseDurationWithSecs(durStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rtCdr.CgrId = utils.Sha1(rtCdr.AccId, rtCdr.SetupTime.String())
|
||||
rtCdr.ExtraFields = make(map[string]string, len(extraFlds))
|
||||
for _, fldName := range extraFlds {
|
||||
if fldVal, hasKey := fsCdr.vars[fldName]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, fldName))
|
||||
} else {
|
||||
rtCdr.ExtraFields[fldName] = fldVal
|
||||
}
|
||||
}
|
||||
return rtCdr, nil
|
||||
func (fsCdr FSCdr) AsStoredCdr() *utils.StoredCdr {
|
||||
storCdr := new(utils.StoredCdr)
|
||||
storCdr.CgrId = fsCdr.getCgrId()
|
||||
storCdr.AccId = fsCdr.vars[FS_UUID]
|
||||
storCdr.CdrHost = fsCdr.vars[FS_IP]
|
||||
storCdr.CdrSource = FS_CDR_SOURCE
|
||||
storCdr.ReqType = utils.FirstNonEmpty(fsCdr.vars[FS_REQTYPE], cfg.DefaultReqType)
|
||||
storCdr.Direction = "*out"
|
||||
storCdr.Tenant = utils.FirstNonEmpty(fsCdr.vars[FS_CSTMID], cfg.DefaultTenant)
|
||||
storCdr.Category = utils.FirstNonEmpty(fsCdr.vars[FS_CATEGORY], cfg.DefaultCategory)
|
||||
storCdr.Account = utils.FirstNonEmpty(fsCdr.vars[FS_ACCOUNT], fsCdr.vars[FS_USERNAME])
|
||||
storCdr.Subject = utils.FirstNonEmpty(fsCdr.vars[FS_SUBJECT], fsCdr.vars[FS_USERNAME])
|
||||
storCdr.Destination = utils.FirstNonEmpty(fsCdr.vars[FS_DESTINATION], fsCdr.vars[FS_CALL_DEST_NR], fsCdr.vars[FS_SIP_REQUSER])
|
||||
storCdr.SetupTime, _ = utils.ParseTimeDetectLayout(fsCdr.vars[FS_SETUP_TIME]) // Not interested to process errors, should do them if necessary in a previous step
|
||||
storCdr.AnswerTime, _ = utils.ParseTimeDetectLayout(fsCdr.vars[FS_ANSWER_TIME])
|
||||
storCdr.Duration, _ = utils.ParseDurationWithSecs(fsCdr.vars[FS_DURATION])
|
||||
storCdr.ExtraFields = fsCdr.getExtraFields()
|
||||
storCdr.Cost = -1
|
||||
return storCdr
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
140
config/config.go
140
config/config.go
@@ -22,6 +22,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -87,36 +88,25 @@ 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>
|
||||
CdreCdrFormat string // Format of the exported CDRs. <csv>
|
||||
CdreMaskDestId string // Id of the destination list to be masked in CDRs
|
||||
CdreMaskLength int // Number of digits to mask in the destination suffix if destination is in the MaskDestinationdsId
|
||||
CdreCostShiftDigits int // Shift digits in the cost on export (eg: convert from EUR to cents)
|
||||
CdreDir string // Path towards exported cdrs directory
|
||||
CdreExportedFields []*utils.RSRField // List of fields in the exported CDRs
|
||||
CdreFWXmlTemplate *CgrXmlCdreFwCfg // Use this configuration as export template in case of fixed fields length
|
||||
CdrcEnabled bool // Enable CDR client functionality
|
||||
CdrcCdrs string // Address where to reach CDR server
|
||||
CdrcCdrsMethod string // Mechanism to use when posting CDRs on server <http_cgr>
|
||||
CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
|
||||
CdrcCdrType string // CDR file format <csv>.
|
||||
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.
|
||||
CdrcAccIdField string // Accounting id field identifier. Use index number in case of .csv cdrs.
|
||||
CdrcReqTypeField string // Request type field identifier. Use index number in case of .csv cdrs.
|
||||
CdrcDirectionField string // Direction field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcTenantField string // Tenant field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcCategoryField string // Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcAccountField string // Account field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcSubjectField string // Subject field identifier. Use index numbers in case of .csv CDRs.
|
||||
CdrcDestinationField string // Destination field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcSetupTimeField string // Setup time field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcAnswerTimeField string // Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcDurationField string // Duration field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcExtraFields []string // Extra fields to extract, special format in case of .csv "field1:index1,field2:index2"
|
||||
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>
|
||||
CdreCdrFormat string // Format of the exported CDRs. <csv>
|
||||
CdreMaskDestId string // Id of the destination list to be masked in CDRs
|
||||
CdreMaskLength int // Number of digits to mask in the destination suffix if destination is in the MaskDestinationdsId
|
||||
CdreCostShiftDigits int // Shift digits in the cost on export (eg: convert from EUR to cents)
|
||||
CdreDir string // Path towards exported cdrs directory
|
||||
CdreExportedFields []*utils.RSRField // List of fields in the exported CDRs
|
||||
CdreFWXmlTemplate *CgrXmlCdreFwCfg // Use this configuration as export template in case of fixed fields length
|
||||
CdrcEnabled bool // Enable CDR client functionality
|
||||
CdrcCdrs string // Address where to reach CDR server
|
||||
CdrcCdrsMethod string // Mechanism to use when posting CDRs on server <http_cgr>
|
||||
CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
|
||||
CdrcCdrType string // CDR file format <csv>.
|
||||
CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored.
|
||||
CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved.
|
||||
CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database.
|
||||
CdrcCdrFields map[string]*utils.RSRField // FieldName/RSRField format. Index number in case of .csv cdrs.
|
||||
SMEnabled bool
|
||||
SMSwitchType string
|
||||
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
|
||||
@@ -188,22 +178,23 @@ func (self *CGRConfig) setDefaults() error {
|
||||
self.CdrcCdrs = utils.INTERNAL
|
||||
self.CdrcCdrsMethod = "http_cgr"
|
||||
self.CdrcRunDelay = time.Duration(0)
|
||||
self.CdrcCdrType = "csv"
|
||||
self.CdrcCdrType = utils.CSV
|
||||
self.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
|
||||
self.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
|
||||
self.CdrcSourceId = "freeswitch_csv"
|
||||
self.CdrcAccIdField = "0"
|
||||
self.CdrcReqTypeField = "1"
|
||||
self.CdrcDirectionField = "2"
|
||||
self.CdrcTenantField = "3"
|
||||
self.CdrcCategoryField = "4"
|
||||
self.CdrcAccountField = "5"
|
||||
self.CdrcSubjectField = "6"
|
||||
self.CdrcDestinationField = "7"
|
||||
self.CdrcSetupTimeField = "8"
|
||||
self.CdrcAnswerTimeField = "9"
|
||||
self.CdrcDurationField = "10"
|
||||
self.CdrcExtraFields = []string{}
|
||||
self.CdrcCdrFields = map[string]*utils.RSRField{
|
||||
utils.ACCID: &utils.RSRField{Id: "0"},
|
||||
utils.REQTYPE: &utils.RSRField{Id: "1"},
|
||||
utils.DIRECTION: &utils.RSRField{Id: "2"},
|
||||
utils.TENANT: &utils.RSRField{Id: "3"},
|
||||
utils.CATEGORY: &utils.RSRField{Id: "4"},
|
||||
utils.ACCOUNT: &utils.RSRField{Id: "5"},
|
||||
utils.SUBJECT: &utils.RSRField{Id: "6"},
|
||||
utils.DESTINATION: &utils.RSRField{Id: "7"},
|
||||
utils.SETUP_TIME: &utils.RSRField{Id: "8"},
|
||||
utils.ANSWER_TIME: &utils.RSRField{Id: "9"},
|
||||
utils.DURATION: &utils.RSRField{Id: "10"},
|
||||
}
|
||||
self.MediatorEnabled = false
|
||||
self.MediatorRater = "internal"
|
||||
self.MediatorRaterReconnects = 3
|
||||
@@ -235,7 +226,7 @@ func (self *CGRConfig) setDefaults() error {
|
||||
&utils.RSRField{Id: utils.REQTYPE},
|
||||
&utils.RSRField{Id: utils.DIRECTION},
|
||||
&utils.RSRField{Id: utils.TENANT},
|
||||
&utils.RSRField{Id: utils.Category},
|
||||
&utils.RSRField{Id: utils.CATEGORY},
|
||||
&utils.RSRField{Id: utils.ACCOUNT},
|
||||
&utils.RSRField{Id: utils.SUBJECT},
|
||||
&utils.RSRField{Id: utils.DESTINATION},
|
||||
@@ -256,6 +247,13 @@ func (self *CGRConfig) checkConfigSanity() error {
|
||||
return errors.New("Need XmlTemplate for fixed_width cdr export")
|
||||
}
|
||||
}
|
||||
if self.CdrcCdrType == utils.CSV {
|
||||
for _, rsrFld := range self.CdrcCdrFields {
|
||||
if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil {
|
||||
return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -483,44 +481,22 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("cdrc", "cdr_source_id"); hasOpt {
|
||||
cfg.CdrcSourceId, _ = c.GetString("cdrc", "cdr_source_id")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "accid_field"); hasOpt {
|
||||
cfg.CdrcAccIdField, _ = c.GetString("cdrc", "accid_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "reqtype_field"); hasOpt {
|
||||
cfg.CdrcReqTypeField, _ = c.GetString("cdrc", "reqtype_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "direction_field"); hasOpt {
|
||||
cfg.CdrcDirectionField, _ = c.GetString("cdrc", "direction_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "tenant_field"); hasOpt {
|
||||
cfg.CdrcTenantField, _ = c.GetString("cdrc", "tenant_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "tor_field"); hasOpt {
|
||||
cfg.CdrcCategoryField, _ = c.GetString("cdrc", "tor_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "account_field"); hasOpt {
|
||||
cfg.CdrcAccountField, _ = c.GetString("cdrc", "account_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "subject_field"); hasOpt {
|
||||
cfg.CdrcSubjectField, _ = c.GetString("cdrc", "subject_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "destination_field"); hasOpt {
|
||||
cfg.CdrcDestinationField, _ = c.GetString("cdrc", "destination_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "setup_time_field"); hasOpt {
|
||||
cfg.CdrcSetupTimeField, _ = c.GetString("cdrc", "setup_time_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "answer_time_field"); hasOpt {
|
||||
cfg.CdrcAnswerTimeField, _ = c.GetString("cdrc", "answer_time_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "duration_field"); hasOpt {
|
||||
cfg.CdrcDurationField, _ = c.GetString("cdrc", "duration_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "extra_fields"); hasOpt {
|
||||
eFldsStr, _ := c.GetString("cdrc", "extra_fields")
|
||||
if cfg.CdrcExtraFields, err = ConfigSlice(eFldsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ParseCdrcCdrFields
|
||||
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", "duration_field")
|
||||
extraFlds, _ := c.GetString("cdrc", "extra_fields")
|
||||
if cfg.CdrcCdrFields, err = ParseCdrcCdrFields(accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld,
|
||||
setupTimeFld, answerTimeFld, durFld, extraFlds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "enabled"); hasOpt {
|
||||
cfg.MediatorEnabled, _ = c.GetBool("mediator", "enabled")
|
||||
|
||||
@@ -95,18 +95,19 @@ func TestDefaults(t *testing.T) {
|
||||
eCfg.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
|
||||
eCfg.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
|
||||
eCfg.CdrcSourceId = "freeswitch_csv"
|
||||
eCfg.CdrcAccIdField = "0"
|
||||
eCfg.CdrcReqTypeField = "1"
|
||||
eCfg.CdrcDirectionField = "2"
|
||||
eCfg.CdrcTenantField = "3"
|
||||
eCfg.CdrcCategoryField = "4"
|
||||
eCfg.CdrcAccountField = "5"
|
||||
eCfg.CdrcSubjectField = "6"
|
||||
eCfg.CdrcDestinationField = "7"
|
||||
eCfg.CdrcSetupTimeField = "8"
|
||||
eCfg.CdrcAnswerTimeField = "9"
|
||||
eCfg.CdrcDurationField = "10"
|
||||
eCfg.CdrcExtraFields = []string{}
|
||||
eCfg.CdrcCdrFields = map[string]*utils.RSRField{
|
||||
utils.ACCID: &utils.RSRField{Id: "0"},
|
||||
utils.REQTYPE: &utils.RSRField{Id: "1"},
|
||||
utils.DIRECTION: &utils.RSRField{Id: "2"},
|
||||
utils.TENANT: &utils.RSRField{Id: "3"},
|
||||
utils.CATEGORY: &utils.RSRField{Id: "4"},
|
||||
utils.ACCOUNT: &utils.RSRField{Id: "5"},
|
||||
utils.SUBJECT: &utils.RSRField{Id: "6"},
|
||||
utils.DESTINATION: &utils.RSRField{Id: "7"},
|
||||
utils.SETUP_TIME: &utils.RSRField{Id: "8"},
|
||||
utils.ANSWER_TIME: &utils.RSRField{Id: "9"},
|
||||
utils.DURATION: &utils.RSRField{Id: "10"},
|
||||
}
|
||||
eCfg.MediatorEnabled = false
|
||||
eCfg.MediatorRater = "internal"
|
||||
eCfg.MediatorRaterReconnects = 3
|
||||
@@ -138,7 +139,7 @@ func TestDefaults(t *testing.T) {
|
||||
&utils.RSRField{Id: utils.REQTYPE},
|
||||
&utils.RSRField{Id: utils.DIRECTION},
|
||||
&utils.RSRField{Id: utils.TENANT},
|
||||
&utils.RSRField{Id: utils.Category},
|
||||
&utils.RSRField{Id: utils.CATEGORY},
|
||||
&utils.RSRField{Id: utils.ACCOUNT},
|
||||
&utils.RSRField{Id: utils.SUBJECT},
|
||||
&utils.RSRField{Id: utils.DESTINATION},
|
||||
@@ -168,6 +169,16 @@ func TestSanityCheck(t *testing.T) {
|
||||
if err := cfg.checkConfigSanity(); err == nil {
|
||||
t.Error("Failed to detect fixed_width dependency on xml configuration")
|
||||
}
|
||||
cfg.CdrcCdrFields = map[string]*utils.RSRField{utils.ACCID: &utils.RSRField{Id: "test"}}
|
||||
if err := cfg.checkConfigSanity(); err == nil {
|
||||
t.Error("Failed to detect improper use of CDR field names")
|
||||
}
|
||||
cfg = &CGRConfig{}
|
||||
cfg.CdrcCdrType = utils.CSV
|
||||
cfg.CdrcCdrFields = map[string]*utils.RSRField{"extra1": &utils.RSRField{Id: "test"}}
|
||||
if err := cfg.checkConfigSanity(); err == nil {
|
||||
t.Error("Failed to detect improper use of CDR field names")
|
||||
}
|
||||
}
|
||||
|
||||
// Load config from file and make sure we have all set
|
||||
@@ -229,18 +240,20 @@ func TestConfigFromFile(t *testing.T) {
|
||||
eCfg.CdrcCdrInDir = "test"
|
||||
eCfg.CdrcCdrOutDir = "test"
|
||||
eCfg.CdrcSourceId = "test"
|
||||
eCfg.CdrcAccIdField = "test"
|
||||
eCfg.CdrcReqTypeField = "test"
|
||||
eCfg.CdrcDirectionField = "test"
|
||||
eCfg.CdrcTenantField = "test"
|
||||
eCfg.CdrcCategoryField = "test"
|
||||
eCfg.CdrcAccountField = "test"
|
||||
eCfg.CdrcSubjectField = "test"
|
||||
eCfg.CdrcDestinationField = "test"
|
||||
eCfg.CdrcSetupTimeField = "test"
|
||||
eCfg.CdrcAnswerTimeField = "test"
|
||||
eCfg.CdrcDurationField = "test"
|
||||
eCfg.CdrcExtraFields = []string{"test"}
|
||||
eCfg.CdrcCdrFields = map[string]*utils.RSRField{
|
||||
utils.ACCID: &utils.RSRField{Id: "test"},
|
||||
utils.REQTYPE: &utils.RSRField{Id: "test"},
|
||||
utils.DIRECTION: &utils.RSRField{Id: "test"},
|
||||
utils.TENANT: &utils.RSRField{Id: "test"},
|
||||
utils.CATEGORY: &utils.RSRField{Id: "test"},
|
||||
utils.ACCOUNT: &utils.RSRField{Id: "test"},
|
||||
utils.SUBJECT: &utils.RSRField{Id: "test"},
|
||||
utils.DESTINATION: &utils.RSRField{Id: "test"},
|
||||
utils.SETUP_TIME: &utils.RSRField{Id: "test"},
|
||||
utils.ANSWER_TIME: &utils.RSRField{Id: "test"},
|
||||
utils.DURATION: &utils.RSRField{Id: "test"},
|
||||
"test": &utils.RSRField{Id: "test"},
|
||||
}
|
||||
eCfg.MediatorEnabled = true
|
||||
eCfg.MediatorRater = "test"
|
||||
eCfg.MediatorRaterReconnects = 99
|
||||
|
||||
@@ -21,6 +21,7 @@ package config
|
||||
import (
|
||||
"code.google.com/p/goconf/conf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
@@ -81,7 +82,7 @@ func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err
|
||||
if tenantFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "tor_fields")
|
||||
cfgVal, _ = c.GetString("derived_charging", "category_fields")
|
||||
if torFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -136,3 +137,38 @@ func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err
|
||||
}
|
||||
return dcs, nil
|
||||
}
|
||||
|
||||
func ParseCdrcCdrFields(accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld,
|
||||
setupTimeFld, answerTimeFld, durFld, extraFlds string) (map[string]*utils.RSRField, error) {
|
||||
cdrcCdrFlds := make(map[string]*utils.RSRField)
|
||||
if len(extraFlds) != 0 {
|
||||
if sepExtraFlds, err := ConfigSlice(extraFlds); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, fldStr := range sepExtraFlds {
|
||||
// extra fields defined as: <label_extrafield_1>:<index_extrafield_1>
|
||||
if spltLbl := strings.Split(fldStr, utils.CONCATENATED_KEY_SEP); len(spltLbl) != 2 {
|
||||
return nil, fmt.Errorf("Wrong format for cdrc.extra_fields: %s", fldStr)
|
||||
} else {
|
||||
if rsrFld, err := utils.NewRSRField(spltLbl[1]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cdrcCdrFlds[spltLbl[0]] = rsrFld
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for fldTag, fldVal := range map[string]string{utils.ACCID: accIdFld, utils.REQTYPE: reqtypeFld, utils.DIRECTION: directionFld, utils.TENANT: tenantFld,
|
||||
utils.CATEGORY: categoryFld, utils.ACCOUNT: acntFld, utils.SUBJECT: subjectFld, utils.DESTINATION: destFld, utils.SETUP_TIME: setupTimeFld,
|
||||
utils.ANSWER_TIME: answerTimeFld, utils.DURATION: durFld} {
|
||||
if len(fldVal) != 0 {
|
||||
if rsrFld, err := utils.NewRSRField(fldVal); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cdrcCdrFlds[fldTag] = rsrFld
|
||||
}
|
||||
}
|
||||
}
|
||||
return cdrcCdrFlds, nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ run_ids = run1, run2
|
||||
reqtype_fields = test1, test2
|
||||
direction_fields = test1, test2
|
||||
tenant_fields = test1, test2
|
||||
tor_fields = test1, test2
|
||||
category_fields = test1, test2
|
||||
account_fields = test1, test2
|
||||
subject_fields = test1, test2
|
||||
destination_fields = test1, test2
|
||||
@@ -63,3 +63,41 @@ duration_fields = test1, test2
|
||||
t.Errorf("Expecting: %v, received: %v", edcs, cfg.DerivedChargers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCdrcCdrFields(t *testing.T) {
|
||||
eFieldsCfg := []byte(`[cdrc]
|
||||
cdr_type = test
|
||||
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
|
||||
duration_field = duration1
|
||||
extra_fields = extra1:extraval1,extra2:extraval1
|
||||
`)
|
||||
eCdrcCdrFlds := map[string]*utils.RSRField{
|
||||
utils.ACCID: &utils.RSRField{Id: "accid1"},
|
||||
utils.REQTYPE: &utils.RSRField{Id: "reqtype1"},
|
||||
utils.DIRECTION: &utils.RSRField{Id: "direction1"},
|
||||
utils.TENANT: &utils.RSRField{Id: "tenant1"},
|
||||
utils.CATEGORY: &utils.RSRField{Id: "category1"},
|
||||
utils.ACCOUNT: &utils.RSRField{Id: "account1"},
|
||||
utils.SUBJECT: &utils.RSRField{Id: "subject1"},
|
||||
utils.DESTINATION: &utils.RSRField{Id: "destination1"},
|
||||
utils.SETUP_TIME: &utils.RSRField{Id: "setuptime1"},
|
||||
utils.ANSWER_TIME: &utils.RSRField{Id: "answertime1"},
|
||||
utils.DURATION: &utils.RSRField{Id: "duration1"},
|
||||
"extra1": &utils.RSRField{Id: "extraval1"},
|
||||
"extra2": &utils.RSRField{Id: "extraval1"},
|
||||
}
|
||||
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.CdrcCdrFields, eCdrcCdrFlds) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdrcCdrFlds, cfg.CdrcCdrFields)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ accountdb_port = test # Accounting subsystem port to reach the database.
|
||||
accountdb_name = test # Accounting subsystem database name to connect to.
|
||||
accountdb_user = test # Accounting subsystem username to use when connecting to database.
|
||||
accountdb_passwd = test # Accounting subsystem password to use when connecting to database.
|
||||
stordb_type = test # Log/stored database type to use: <same|postgres|mongo|redis>
|
||||
stordb_type = test # Log/scategoryed database type to use: <same|postgres|mongo|redis>
|
||||
stordb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
stordb_port = test # The port to reach the logdb.
|
||||
stordb_name = test # The name of the log database to connect to.
|
||||
stordb_user = test # Username to use when connecting to logdb.
|
||||
stordb_passwd = test # Password to use when connecting to logdb.
|
||||
dbdata_encoding = test # The encoding used to store object data in strings: <msgpack|json>
|
||||
dbdata_encoding = test # The encoding used to scategorye object data in strings: <msgpack|json>
|
||||
rpc_json_listen = test # RPC JSON listening address
|
||||
rpc_gob_listen = test # RPC GOB listening address
|
||||
http_listen = test # HTTP listening address
|
||||
@@ -44,8 +44,8 @@ enabled = true # Starts Scheduler service: <true|false>.
|
||||
|
||||
[cdrs]
|
||||
enabled = true # Start the CDR Server service: <true|false>.
|
||||
extra_fields = test # Extra fields to store in CDRs
|
||||
mediator = test # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
extra_fields = test # Extra fields to scategorye in CDRs
|
||||
mediator = test # Address where to reach the Mediacategory. Empty for disabling mediation. <""|internal>
|
||||
|
||||
[cdre]
|
||||
cdr_format = test # Exported CDRs format <csv>
|
||||
@@ -61,24 +61,24 @@ cdrs = test # Address where to reach CDR server
|
||||
cdrs_method = test # Mechanism to use when posting CDRs on server <http_cgr>
|
||||
run_delay = 99 # Period to sleep between two runs, 0 to use automation via inotify
|
||||
cdr_type = test # CDR file format <csv>.
|
||||
cdr_in_dir = test # Absolute path towards the directory where the CDRs are kept (file stored CDRs).
|
||||
cdr_out_dir = test # Absolute path towards the directory where processed CDRs will be moved after processing.
|
||||
cdr_in_dir = test # Absolute path towards the direccategoryy where the CDRs are kept (file scategoryed CDRs).
|
||||
cdr_out_dir = test # Absolute path towards the direccategoryy where processed CDRs will be moved after processing.
|
||||
cdr_source_id = test # Tag identifying the source of the CDRs within CGRS database.
|
||||
accid_field = test # Accounting id field identifier. Use index number in case of .csv cdrs.
|
||||
reqtype_field = test # Request type field identifier. Use index number in case of .csv cdrs.
|
||||
direction_field = test # Direction field identifier. Use index numbers in case of .csv cdrs.
|
||||
tenant_field = test # Tenant field identifier. Use index numbers in case of .csv cdrs.
|
||||
tor_field = test # Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
category_field = test # Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
account_field = test # Account field identifier. Use index numbers in case of .csv cdrs.
|
||||
subject_field = test # Subject field identifier. Use index numbers in case of .csv CDRs.
|
||||
destination_field = test # Destination field identifier. Use index numbers in case of .csv cdrs.
|
||||
setup_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
answer_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
duration_field = test # Duration field identifier. Use index numbers in case of .csv cdrs.
|
||||
extra_fields = test # Field identifiers of the fields to add in extra fields section, special format in case of .csv "index1:field1,index2:field2"
|
||||
extra_fields = test:test # Field identifiers of the fields to add in extra fields section, special format in case of .csv "index1|field1,index2|field2"
|
||||
|
||||
[mediator]
|
||||
enabled = true # Starts Mediator service: <true|false>.
|
||||
enabled = true # Starts Mediacategory service: <true|false>.
|
||||
rater = test # Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
rater_reconnects = 99 # Number of reconnects to rater before giving up.
|
||||
|
||||
@@ -100,7 +100,7 @@ run_ids = test # Identifiers of additional sessions control.
|
||||
reqtype_fields = test # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
direction_fields = test # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
tenant_fields = test # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
tor_fields = test # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
category_fields = test # Name of category fields to be used during additional sessions control <""|*default|field_name>.
|
||||
account_fields = test # Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
subject_fields = test # Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
destination_fields = test # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
@@ -110,13 +110,13 @@ duration_fields = test # Name of duration fields to be used during additional s
|
||||
combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.
|
||||
|
||||
[history_server]
|
||||
enabled = true # Starts History service: <true|false>.
|
||||
history_dir = test # Location on disk where to store history files.
|
||||
enabled = true # Starts Hiscategoryy service: <true|false>.
|
||||
history_dir = test # Location on disk where to scategorye hiscategoryy files.
|
||||
save_interval = 99 # Timeout duration between saves
|
||||
|
||||
[history_agent]
|
||||
enabled = true # Starts History as a client: <true|false>.
|
||||
server = test # Address where to reach the master history server: <internal|x.y.z.y:1234>
|
||||
enabled = true # Starts Hiscategoryy as a client: <true|false>.
|
||||
server = test # Address where to reach the master hiscategoryy server: <internal|x.y.z.y:1234>
|
||||
|
||||
[mailer]
|
||||
server = test # The server to use when sending emails out
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
# rpc_gob_listen = 127.0.0.1:2013 # RPC GOB listening address
|
||||
# http_listen = 127.0.0.1:2080 # HTTP listening address
|
||||
# default_reqtype = rated # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>.
|
||||
# default_tor = call # Default Type of Record to consider when missing from requests.
|
||||
# default_category = call # Default Type of Record to consider when missing from requests.
|
||||
# default_tenant = cgrates.org # Default Tenant to consider when missing from requests.
|
||||
# default_subject = cgrates # Default rating Subject to consider when missing from requests.
|
||||
# rounding_method = *middle # Rounding method for floats/costs: <*up|*middle|*down>
|
||||
@@ -56,7 +56,7 @@
|
||||
# mask_length = 0 # Length of the destination suffix to be masked
|
||||
# cost_shift_digits = 0 # Shift cost on export with the number of digits digits defined here (eg: convert from Eur to cent).
|
||||
# export_dir = /var/log/cgrates/cdrexport/csv # Path where the exported CDRs will be placed
|
||||
# export_template = cgrid,mediation_runid,accid,cdrhost,reqtype,direction,tenant,tor,account,subject,destination,setup_time,answer_time,duration,cost
|
||||
# export_template = cgrid,mediation_runid,accid,cdrhost,reqtype,direction,tenant,category,account,subject,destination,setup_time,answer_time,duration,cost
|
||||
# Exported fields template <""|fld1,fld2|*xml:instance_name>
|
||||
[cdrc]
|
||||
# enabled = false # Enable CDR client functionality
|
||||
@@ -71,7 +71,7 @@
|
||||
# reqtype_field = 1 # Request type field identifier. Use index number in case of .csv cdrs.
|
||||
# direction_field = 2 # Direction field identifier. Use index numbers in case of .csv cdrs.
|
||||
# tenant_field = 3 # Tenant field identifier. Use index numbers in case of .csv cdrs.
|
||||
# tor_field = 4 # Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
# category_field = 4 # Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
# account_field = 5 # Account field identifier. Use index numbers in case of .csv cdrs.
|
||||
# subject_field = 6 # Subject field identifier. Use index numbers in case of .csv CDRs.
|
||||
# destination_field = 7 # Destination field identifier. Use index numbers in case of .csv cdrs.
|
||||
@@ -103,7 +103,7 @@
|
||||
# reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# tor_fields = # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# category_fields = # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# account_fields = # Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# subject_fields = # Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# destination_fields = # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
|
||||
@@ -9,7 +9,7 @@ CREATE TABLE cdrs_primary (
|
||||
reqtype varchar(24) NOT NULL,
|
||||
direction varchar(8) NOT NULL,
|
||||
tenant varchar(64) NOT NULL,
|
||||
tor varchar(16) NOT NULL,
|
||||
category varchar(16) NOT NULL,
|
||||
account varchar(128) NOT NULL,
|
||||
subject varchar(128) NOT NULL,
|
||||
destination varchar(128) NOT NULL,
|
||||
|
||||
@@ -10,7 +10,7 @@ CREATE TABLE `cost_details` (
|
||||
`accid` varchar(64) NOT NULL,
|
||||
`direction` varchar(8) NOT NULL,
|
||||
`tenant` varchar(128) NOT NULL,
|
||||
`tor` varchar(32) NOT NULL,
|
||||
`category` varchar(32) NOT NULL,
|
||||
`account` varchar(128) NOT NULL,
|
||||
`subject` varchar(128) NOT NULL,
|
||||
`destination` varchar(128) NOT NULL,
|
||||
|
||||
@@ -100,7 +100,7 @@ CREATE TABLE `tp_rating_profiles` (
|
||||
`tpid` varchar(64) NOT NULL,
|
||||
`loadid` varchar(64) NOT NULL,
|
||||
`tenant` varchar(64) NOT NULL,
|
||||
`tor` varchar(16) NOT NULL,
|
||||
`category` varchar(16) NOT NULL,
|
||||
`direction` varchar(8) NOT NULL,
|
||||
`subject` varchar(64) NOT NULL,
|
||||
`activation_time` varchar(24) NOT NULL,
|
||||
@@ -108,7 +108,7 @@ CREATE TABLE `tp_rating_profiles` (
|
||||
`fallback_subjects` varchar(64),
|
||||
PRIMARY KEY (`tbid`),
|
||||
KEY `tpid_loadid` (`tpid`, `loadid`),
|
||||
UNIQUE KEY `tpid_loadid_tenant_tor_dir_subj_atime` (`tpid`,`loadid`, `tenant`,`tor`,`direction`,`subject`,`activation_time`)
|
||||
UNIQUE KEY `tpid_loadid_tenant_category_dir_subj_atime` (`tpid`,`loadid`, `tenant`,`category`,`direction`,`subject`,`activation_time`)
|
||||
);
|
||||
|
||||
--
|
||||
@@ -215,14 +215,14 @@ CREATE TABLE `tp_account_actions` (
|
||||
-- Table structure for table `tp_lcrs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS tp_lcrs;
|
||||
CREATE TABLE tp_lcrs (
|
||||
DROP TABLE IF EXISTS tp_lcr_rules;
|
||||
CREATE TABLE tp_lcr (
|
||||
`tbid` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`tpid` varchar(64) NOT NULL,
|
||||
`direction` varchar(8) NOT NULL,
|
||||
`tenant` varchar(64) NOT NULL,
|
||||
`customer` varchar(64) NOT NULL,
|
||||
`destination`_id varchar(64) NOT NULL,
|
||||
`destination_id` varchar(64) NOT NULL,
|
||||
`category` varchar(16) NOT NULL,
|
||||
`strategy` varchar(16) NOT NULL,
|
||||
`suppliers` varchar(64) NOT NULL,
|
||||
@@ -250,12 +250,12 @@ CREATE TABLE tp_derived_chargers (
|
||||
`reqtype_field` varchar(24) NOT NULL,
|
||||
`direction_field` varchar(24) NOT NULL,
|
||||
`tenant_field` varchar(24) NOT NULL,
|
||||
`tor_field` varchar(24) NOT NULL,
|
||||
`category_field` varchar(24) NOT NULL,
|
||||
`account_field` varchar(24) NOT NULL,
|
||||
`subject_field` varchar(24) NOT NULL,
|
||||
`destination_field` varchar(24) NOT NULL,
|
||||
`setup_time`_field varchar(24) NOT NULL,
|
||||
`answer_time`_field varchar(24) NOT NULL,
|
||||
`setup_time_field` varchar(24) NOT NULL,
|
||||
`answer_time_field` varchar(24) NOT NULL,
|
||||
`duration_field` varchar(24) NOT NULL,
|
||||
PRIMARY KEY (`tbid`),
|
||||
KEY `tpid` (`tpid`)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#Tag,BalanceType,Direction,ThresholdType,ThresholdValue,DestinationTag,ActionsTag,Weight
|
||||
#Tag,BalanceType,Direction,ThresholdType,ThresholdValue,Recurrent,DestinationTag,ActionsTag,Weight
|
||||
STANDARD_TRIGGERS,*monetary,*out,*min_balance,2,false,,LOG_BALANCE,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*max_balance,20,false,,LOG_BALANCE,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*max_counter,15,false,FS_USERS,LOG_BALANCE,10
|
||||
|
||||
|
@@ -161,8 +161,10 @@ func (csvr *CSVReader) ShowStatistics() {
|
||||
}
|
||||
// actions
|
||||
log.Print("Actions: ", len(csvr.actions))
|
||||
// action timings
|
||||
// action plans
|
||||
log.Print("Action plans: ", len(csvr.actionsTimings))
|
||||
// action trigers
|
||||
log.Print("Action trigers: ", len(csvr.actionsTriggers))
|
||||
// account actions
|
||||
log.Print("Account actions: ", len(csvr.accountActions))
|
||||
// derivedChargers
|
||||
|
||||
@@ -210,8 +210,8 @@ var FileValidators = map[string]*FileLineRegexValidator{
|
||||
regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:\d+\.?\d*){1}`),
|
||||
"Tag([0-9A-Za-z_]),ActionsTag([0-9A-Za-z_]),TimingTag([0-9A-Za-z_]),Weight([0-9.])"},
|
||||
utils.ACTION_TRIGGERS_CSV: &FileLineRegexValidator{utils.ACTION_TRIGGERS_NRCOLS,
|
||||
regexp.MustCompile(`(?:\w+),(?:\*\w+),(?:\*out),(?:\*\w+),(?:\d+\.?\d*),(?:\w+|\*any)?,(?:\w+),(?:\d+\.?\d*)$`),
|
||||
"Tag([0-9A-Za-z_]),BalanceType(*[a-z_]),Direction(*out),ThresholdType(*[a-z_]),ThresholdValue([0-9]+),DestinationTag([0-9A-Za-z_]|*all),ActionsTag([0-9A-Za-z_]),Weight([0-9]+)"},
|
||||
regexp.MustCompile(`(?:\w+),(?:\*\w+),(?:\*out),(?:\*\w+),(?:\d+\.?\d*),(?:true|false)?,(?:\w+|\*any)?,(?:\w+),(?:\d+\.?\d*)$`),
|
||||
"Tag([0-9A-Za-z_]),BalanceType(*[a-z_]),Direction(*out),ThresholdType(*[a-z_]),ThresholdValue([0-9]+),Recurrent(true|false),DestinationTag([0-9A-Za-z_]|*all),ActionsTag([0-9A-Za-z_]),Weight([0-9]+)"},
|
||||
utils.ACCOUNT_ACTIONS_CSV: &FileLineRegexValidator{utils.ACCOUNT_ACTIONS_NRCOLS,
|
||||
regexp.MustCompile(`(?:\w+\s*),(?:(\w+;?)+\s*),(?:\*out\s*),(?:\w+\s*),(?:\w+\s*)$`),
|
||||
"Tenant([0-9A-Za-z_]),Account([0-9A-Za-z_.]),Direction(*out),ActionTimingsTag([0-9A-Za-z_]),ActionTriggersTag([0-9A-Za-z_])"},
|
||||
|
||||
@@ -73,9 +73,9 @@ DUMMY,INVALID;DATA
|
||||
`
|
||||
|
||||
var actionTriggersSample = `#Tag,BalanceType,Direction,ThresholdType,ThresholdValue,DestinationTag,ActionsTag,Weight
|
||||
STANDARD_TRIGGERS,*monetary,*out,*min_balance,2,,LOG_BALANCE,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*max_balance,20,,LOG_BALANCE,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*max_counter,15,FS_USERS,LOG_BALANCE,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*min_balance,2,false,,LOG_BALANCE,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*max_balance,20,false,,LOG_BALANCE,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*max_counter,15,false,FS_USERS,LOG_BALANCE,10
|
||||
DUMMY,INVALID;DATA
|
||||
`
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package engine
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
@@ -223,6 +224,7 @@ func TestLoadFromStorDb(t *testing.T) {
|
||||
t.Error("Failed loading action triggers: ", err.Error())
|
||||
}
|
||||
if err := loader.LoadAccountActions(); err != nil {
|
||||
fmt.Printf("Have actionTriggers loaded :%v\n", loader.actionsTriggers)
|
||||
t.Error("Failed loading account actions: ", err.Error())
|
||||
}
|
||||
if err := loader.WriteToDatabase(true, false); err != nil {
|
||||
|
||||
@@ -110,7 +110,7 @@ type AccountingStorage interface {
|
||||
|
||||
type CdrStorage interface {
|
||||
Storage
|
||||
SetCdr(utils.RawCDR) error
|
||||
SetCdr(*utils.StoredCdr) error
|
||||
SetRatedCdr(*utils.StoredCdr, string) error
|
||||
GetStoredCdrs([]string, []string, []string, []string, []string, []string, []string, []string, []string, []string, []string,
|
||||
int64, int64, time.Time, time.Time, bool, bool) ([]*utils.StoredCdr, error)
|
||||
|
||||
@@ -128,7 +128,7 @@ func (self *SQLStorage) RemTPData(table, tpid string, args ...string) error {
|
||||
q := fmt.Sprintf("DELETE FROM %s WHERE tpid='%s' AND id='%s'", table, tpid, args[0])
|
||||
switch table {
|
||||
case utils.TBL_TP_RATE_PROFILES:
|
||||
q = fmt.Sprintf("DELETE FROM %s WHERE tpid='%s' AND loadid='%s' AND tenant='%s' AND tor='%s' AND direction='%s' AND subject='%s'",
|
||||
q = fmt.Sprintf("DELETE FROM %s WHERE tpid='%s' AND loadid='%s' AND tenant='%s' AND category='%s' AND direction='%s' AND subject='%s'",
|
||||
table, tpid, args[0], args[1], args[2], args[3], args[4])
|
||||
case utils.TBL_TP_ACCOUNT_ACTIONS:
|
||||
q = fmt.Sprintf("DELETE FROM %s WHERE tpid='%s' AND loadid='%s' AND tenant='%s' AND account='%s' AND direction='%s'",
|
||||
@@ -261,7 +261,7 @@ func (self *SQLStorage) SetTPRatingProfiles(tpid string, rps map[string]*utils.T
|
||||
return nil //Nothing to set
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(fmt.Sprintf("INSERT INTO %s (tpid,loadid,tenant,tor,direction,subject,activation_time,rating_plan_id,fallback_subjects) VALUES ",
|
||||
buffer.WriteString(fmt.Sprintf("INSERT INTO %s (tpid,loadid,tenant,category,direction,subject,activation_time,rating_plan_id,fallback_subjects) VALUES ",
|
||||
utils.TBL_TP_RATE_PROFILES))
|
||||
i := 0
|
||||
for _, rp := range rps {
|
||||
@@ -442,7 +442,7 @@ func (self *SQLStorage) SetTPActionTriggers(tpid string, ats map[string][]*utils
|
||||
return nil //Nothing to set
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(fmt.Sprintf("INSERT INTO %s (tpid,id,balance_type,direction,threshold_type,threshold_value,destination_id,actions_id,weight) VALUES ",
|
||||
buffer.WriteString(fmt.Sprintf("INSERT INTO %s (tpid,id,balance_type,direction,threshold_type,threshold_value,recurrent,destination_id,actions_id,weight) VALUES ",
|
||||
utils.TBL_TP_ACTION_TRIGGERS))
|
||||
i := 0
|
||||
for atId, atRows := range ats {
|
||||
@@ -450,13 +450,13 @@ func (self *SQLStorage) SetTPActionTriggers(tpid string, ats map[string][]*utils
|
||||
if i != 0 { //Consecutive values after the first will be prefixed with "," as separator
|
||||
buffer.WriteRune(',')
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("('%s','%s','%s','%s','%s', %f, '%s','%s',%f)",
|
||||
buffer.WriteString(fmt.Sprintf("('%s','%s','%s','%s','%s', %f, %t, '%s','%s',%f)",
|
||||
tpid, atId, atsRow.BalanceType, atsRow.Direction, atsRow.ThresholdType,
|
||||
atsRow.ThresholdValue, atsRow.DestinationId, atsRow.ActionsId, atsRow.Weight))
|
||||
atsRow.ThresholdValue, atsRow.Recurrent, atsRow.DestinationId, atsRow.ActionsId, atsRow.Weight))
|
||||
i++
|
||||
}
|
||||
}
|
||||
buffer.WriteString(" ON DUPLICATE KEY UPDATE weight=values(weight)")
|
||||
buffer.WriteString(" ON DUPLICATE KEY UPDATE recurrent=values(recurrent), weight=values(weight)")
|
||||
if _, err := self.Db.Exec(buffer.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -496,7 +496,7 @@ func (self *SQLStorage) LogCallCost(cgrid, source, runid string, cc *CallCost) (
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("Error marshalling timespans to json: %v", err))
|
||||
}
|
||||
_, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s (cgrid, direction, tenant, tor, account, subject, destination, cost, timespans, source, runid, cost_time)VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', %f, '%s','%s','%s',now()) ON DUPLICATE KEY UPDATE direction=values(direction), tenant=values(tenant), tor=values(tor), account=values(account), subject=values(subject), destination=values(destination), cost=values(cost), timespans=values(timespans), source=values(source), cost_time=now()",
|
||||
_, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s (cgrid, direction, tenant, category, account, subject, destination, cost, timespans, source, runid, cost_time) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', %f, '%s','%s','%s',now()) ON DUPLICATE KEY UPDATE direction=values(direction), tenant=values(tenant), category=values(category), account=values(account), subject=values(subject), destination=values(destination), cost=values(cost), timespans=values(timespans), source=values(source), cost_time=now()",
|
||||
utils.TBL_COST_DETAILS,
|
||||
cgrid,
|
||||
cc.Direction,
|
||||
@@ -516,7 +516,7 @@ func (self *SQLStorage) LogCallCost(cgrid, source, runid string, cc *CallCost) (
|
||||
}
|
||||
|
||||
func (self *SQLStorage) GetCallCostLog(cgrid, source, runid string) (cc *CallCost, err error) {
|
||||
qry := fmt.Sprintf("SELECT cgrid, direction, tenant, tor, account, subject, destination, cost, timespans, source FROM %s WHERE cgrid='%s' AND runid='%s'",
|
||||
qry := fmt.Sprintf("SELECT cgrid, direction, tenant, category, account, subject, destination, cost, timespans, source FROM %s WHERE cgrid='%s' AND runid='%s'",
|
||||
utils.TBL_COST_DETAILS, cgrid, runid)
|
||||
if len(source) != 0 {
|
||||
qry += fmt.Sprintf(" AND source='%s'", source)
|
||||
@@ -544,38 +544,34 @@ func (self *SQLStorage) LogActionTiming(source string, at *ActionTiming, as Acti
|
||||
}
|
||||
func (self *SQLStorage) LogError(uuid, source, runid, errstr string) (err error) { return }
|
||||
|
||||
func (self *SQLStorage) SetCdr(cdr utils.RawCDR) (err error) {
|
||||
// map[account:1001 direction:out orig_ip:172.16.1.1 tor:call accid:accid23 answer_time:2013-02-03 19:54:00 cdrsource:freeswitch_csv destination:+4986517174963 duration:62 reqtype:prepaid subject:1001 supplier:supplier1 tenant:cgrates.org]
|
||||
setupTime, _ := cdr.GetSetupTime() // Ignore errors, we want to store the cdr no matter what
|
||||
answerTime, _ := cdr.GetAnswerTime() // Ignore errors, we want to store the cdr no matter what
|
||||
dur, _ := cdr.GetDuration()
|
||||
func (self *SQLStorage) SetCdr(cdr *utils.StoredCdr) (err error) {
|
||||
_, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s VALUES (NULL,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s', %d)",
|
||||
utils.TBL_CDRS_PRIMARY,
|
||||
cdr.GetCgrId(),
|
||||
cdr.GetAccId(),
|
||||
cdr.GetCdrHost(),
|
||||
cdr.GetCdrSource(),
|
||||
cdr.GetReqType(),
|
||||
cdr.GetDirection(),
|
||||
cdr.GetTenant(),
|
||||
cdr.GetCategory(),
|
||||
cdr.GetAccount(),
|
||||
cdr.GetSubject(),
|
||||
cdr.GetDestination(),
|
||||
setupTime,
|
||||
answerTime,
|
||||
dur,
|
||||
cdr.CgrId,
|
||||
cdr.AccId,
|
||||
cdr.CdrHost,
|
||||
cdr.CdrSource,
|
||||
cdr.ReqType,
|
||||
cdr.Direction,
|
||||
cdr.Tenant,
|
||||
cdr.Category,
|
||||
cdr.Account,
|
||||
cdr.Subject,
|
||||
cdr.Destination,
|
||||
cdr.SetupTime,
|
||||
cdr.AnswerTime,
|
||||
cdr.Duration,
|
||||
))
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("failed to execute cdr insert statement: %v", err))
|
||||
}
|
||||
extraFields, err := json.Marshal(cdr.GetExtraFields())
|
||||
extraFields, err := json.Marshal(cdr.ExtraFields)
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("Error marshalling cdr extra fields to json: %v", err))
|
||||
}
|
||||
_, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s VALUES ('NULL','%s', '%s')",
|
||||
utils.TBL_CDRS_EXTRA,
|
||||
cdr.GetCgrId(),
|
||||
cdr.CgrId,
|
||||
extraFields,
|
||||
))
|
||||
if err != nil {
|
||||
@@ -602,10 +598,10 @@ func (self *SQLStorage) SetRatedCdr(storedCdr *utils.StoredCdr, extraInfo string
|
||||
// Return a slice of CDRs from storDb using optional filters.a
|
||||
// ignoreErr - do not consider cdrs with rating errors
|
||||
// ignoreRated - do not consider cdrs which were already rated, including here the ones with errors
|
||||
func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, cdrHosts, cdrSources, reqTypes, directions, tenants, tors, accounts, subjects, destPrefixes []string, orderIdStart, orderIdEnd int64,
|
||||
func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string, orderIdStart, orderIdEnd int64,
|
||||
timeStart, timeEnd time.Time, ignoreErr, ignoreRated bool) ([]*utils.StoredCdr, error) {
|
||||
var cdrs []*utils.StoredCdr
|
||||
q := bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,accid,cdrhost,cdrsource,reqtype,direction,tenant,tor,account,%s.subject,destination,setup_time,answer_time,duration,extra_fields,runid,cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid", utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, utils.TBL_CDRS_PRIMARY, utils.TBL_RATED_CDRS))
|
||||
q := bytes.NewBufferString(fmt.Sprintf("SELECT %s.cgrid,%s.tbid,accid,cdrhost,cdrsource,reqtype,direction,tenant,category,account,%s.subject,destination,setup_time,answer_time,duration,extra_fields,runid,cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid", utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, utils.TBL_CDRS_PRIMARY, utils.TBL_RATED_CDRS))
|
||||
fltr := new(bytes.Buffer)
|
||||
if len(cgrIds) != 0 {
|
||||
qIds := bytes.NewBufferString(" (")
|
||||
@@ -705,13 +701,13 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, cdrHosts, cdrSources, reqT
|
||||
}
|
||||
fltr.Write(qIds.Bytes())
|
||||
}
|
||||
if len(tors) != 0 {
|
||||
if len(categories) != 0 {
|
||||
qIds := bytes.NewBufferString(" (")
|
||||
for idx, tor := range tors {
|
||||
for idx, category := range categories {
|
||||
if idx != 0 {
|
||||
qIds.WriteString(" OR")
|
||||
}
|
||||
qIds.WriteString(fmt.Sprintf(" tor='%s'", tor))
|
||||
qIds.WriteString(fmt.Sprintf(" category='%s'", category))
|
||||
}
|
||||
qIds.WriteString(" )")
|
||||
if fltr.Len() != 0 {
|
||||
@@ -809,14 +805,14 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, cdrHosts, cdrSources, reqT
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var cgrid, accid, cdrhost, cdrsrc, reqtype, direction, tenant, tor, account, subject, destination string
|
||||
var cgrid, accid, cdrhost, cdrsrc, reqtype, direction, tenant, category, account, subject, destination string
|
||||
var extraFields []byte
|
||||
var setupTime, answerTime time.Time
|
||||
var runid sql.NullString // So we can export unmediated CDRs
|
||||
var orderid, duration int64
|
||||
var cost sql.NullFloat64 // So we can export unmediated CDRs
|
||||
var extraFieldsMp map[string]string
|
||||
if err := rows.Scan(&cgrid, &orderid, &accid, &cdrhost, &cdrsrc, &reqtype, &direction, &tenant, &tor, &account, &subject, &destination, &setupTime, &answerTime, &duration,
|
||||
if err := rows.Scan(&cgrid, &orderid, &accid, &cdrhost, &cdrsrc, &reqtype, &direction, &tenant, &category, &account, &subject, &destination, &setupTime, &answerTime, &duration,
|
||||
&extraFields, &runid, &cost); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -825,7 +821,7 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, cdrHosts, cdrSources, reqT
|
||||
}
|
||||
storCdr := &utils.StoredCdr{
|
||||
CgrId: cgrid, OrderId: orderid, AccId: accid, CdrHost: cdrhost, CdrSource: cdrsrc, ReqType: reqtype, Direction: direction, Tenant: tenant,
|
||||
Category: tor, Account: account, Subject: subject, Destination: destination, SetupTime: setupTime, AnswerTime: answerTime, Duration: time.Duration(duration),
|
||||
Category: category, Account: account, Subject: subject, Destination: destination, SetupTime: setupTime, AnswerTime: answerTime, Duration: time.Duration(duration),
|
||||
ExtraFields: extraFieldsMp, MediationRunId: runid.String, Cost: cost.Float64,
|
||||
}
|
||||
cdrs = append(cdrs, storCdr)
|
||||
@@ -1030,7 +1026,7 @@ func (self *SQLStorage) GetTpRatingPlans(tpid, tag string) (map[string][]*utils.
|
||||
}
|
||||
|
||||
func (self *SQLStorage) GetTpRatingProfiles(qryRpf *utils.TPRatingProfile) (map[string]*utils.TPRatingProfile, error) {
|
||||
q := fmt.Sprintf("SELECT loadid,direction,tenant,tor,subject,activation_time,rating_plan_id,fallback_subjects FROM %s WHERE tpid='%s'",
|
||||
q := fmt.Sprintf("SELECT loadid,direction,tenant,category,subject,activation_time,rating_plan_id,fallback_subjects FROM %s WHERE tpid='%s'",
|
||||
utils.TBL_TP_RATE_PROFILES, qryRpf.TPid)
|
||||
if len(qryRpf.LoadId) != 0 {
|
||||
q += fmt.Sprintf(" AND loadid='%s'", qryRpf.LoadId)
|
||||
@@ -1039,7 +1035,7 @@ func (self *SQLStorage) GetTpRatingProfiles(qryRpf *utils.TPRatingProfile) (map[
|
||||
q += fmt.Sprintf(" AND tenant='%s'", qryRpf.Tenant)
|
||||
}
|
||||
if len(qryRpf.Category) != 0 {
|
||||
q += fmt.Sprintf(" AND tor='%s'", qryRpf.Category)
|
||||
q += fmt.Sprintf(" AND category='%s'", qryRpf.Category)
|
||||
}
|
||||
if len(qryRpf.Direction) != 0 {
|
||||
q += fmt.Sprintf(" AND direction='%s'", qryRpf.Direction)
|
||||
@@ -1054,11 +1050,11 @@ func (self *SQLStorage) GetTpRatingProfiles(qryRpf *utils.TPRatingProfile) (map[
|
||||
defer rows.Close()
|
||||
rpfs := make(map[string]*utils.TPRatingProfile)
|
||||
for rows.Next() {
|
||||
var rcvLoadId, tenant, tor, direction, subject, fallback_subjects, rating_plan_tag, activation_time string
|
||||
if err := rows.Scan(&rcvLoadId, &tenant, &tor, &direction, &subject, &activation_time, &rating_plan_tag, &fallback_subjects); err != nil {
|
||||
var rcvLoadId, tenant, category, direction, subject, fallback_subjects, rating_plan_tag, activation_time string
|
||||
if err := rows.Scan(&rcvLoadId, &tenant, &category, &direction, &subject, &activation_time, &rating_plan_tag, &fallback_subjects); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rp := &utils.TPRatingProfile{TPid: qryRpf.TPid, LoadId: rcvLoadId, Tenant: tenant, Category: tor, Direction: direction, Subject: subject}
|
||||
rp := &utils.TPRatingProfile{TPid: qryRpf.TPid, LoadId: rcvLoadId, Tenant: tenant, Category: category, Direction: direction, Subject: subject}
|
||||
if existingRp, has := rpfs[rp.KeyId()]; !has {
|
||||
rp.RatingPlanActivations = []*utils.TPRatingActivation{
|
||||
&utils.TPRatingActivation{ActivationTime: activation_time, RatingPlanId: rating_plan_tag, FallbackSubjects: fallback_subjects}}
|
||||
@@ -1203,7 +1199,7 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*utils.TPAc
|
||||
|
||||
func (self *SQLStorage) GetTpActionTriggers(tpid, tag string) (map[string][]*utils.TPActionTrigger, error) {
|
||||
ats := make(map[string][]*utils.TPActionTrigger)
|
||||
q := fmt.Sprintf("SELECT tpid,id,balance_type,direction,threshold_type,threshold_value,destination_id,actions_id,weight FROM %s WHERE tpid='%s'",
|
||||
q := fmt.Sprintf("SELECT tpid,id,balance_type,direction,threshold_type,threshold_value,recurrent,destination_id,actions_id,weight FROM %s WHERE tpid='%s'",
|
||||
utils.TBL_TP_ACTION_TRIGGERS, tpid)
|
||||
if tag != "" {
|
||||
q += fmt.Sprintf(" AND id='%s'", tag)
|
||||
@@ -1226,6 +1222,7 @@ func (self *SQLStorage) GetTpActionTriggers(tpid, tag string) (map[string][]*uti
|
||||
Direction: direction,
|
||||
ThresholdType: thresholdType,
|
||||
ThresholdValue: threshold,
|
||||
Recurrent: recurrent,
|
||||
DestinationId: destinations_tag,
|
||||
ActionsId: actions_tag,
|
||||
Weight: weight,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
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
|
||||
@@ -153,7 +153,7 @@ func TestSetCdr(t *testing.T) {
|
||||
"account": "1002", "subject": "1002", "destination": "+4986517174963", "setup_time": "2013-11-07T08:42:25Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "15s",
|
||||
"field_extr1": "val_extr1", "fieldextr2": "valextr2", "cdrsource": TEST_SQL}
|
||||
for _, cdr := range []*utils.CgrCdr{cgrCdr1, cgrCdr2, cgrCdr3, cgrCdr4, cgrCdr5} {
|
||||
if err := mysql.SetCdr(cdr); err != nil {
|
||||
if err := mysql.SetCdr(cdr.AsStoredCdr()); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
@@ -470,3 +470,28 @@ func TestRemStoredCdrs(t *testing.T) {
|
||||
t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetGetTPActionTriggers(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
atrg := &utils.TPActionTrigger{
|
||||
BalanceType: "*monetary",
|
||||
Direction: "*out",
|
||||
ThresholdType: "*min_balance",
|
||||
ThresholdValue: 2.0,
|
||||
Recurrent: true,
|
||||
DestinationId: "*any",
|
||||
Weight: 10.0,
|
||||
ActionsId: "LOG_BALANCE",
|
||||
}
|
||||
mpAtrgs := map[string][]*utils.TPActionTrigger{TEST_SQL: []*utils.TPActionTrigger{atrg}}
|
||||
if err := mysql.SetTPActionTriggers(TEST_SQL, mpAtrgs); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
}
|
||||
if rcvMpAtrgs, err := mysql.GetTpActionTriggers(TEST_SQL, TEST_SQL); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(mpAtrgs, rcvMpAtrgs) {
|
||||
t.Errorf("Expecting: %v, received: %v", mpAtrgs, rcvMpAtrgs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ func (self *TPCSVImporter) importActionTriggers(fn string) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
tag, balanceType, direction, thresholdType, destinationTag, actionsTag := record[0], record[1], record[2], record[3], record[5], record[6]
|
||||
tag, balanceType, direction, thresholdType, destinationTag, actionsTag := record[0], record[1], record[2], record[3], record[6], record[7]
|
||||
threshold, err := strconv.ParseFloat(record[4], 64)
|
||||
if err != nil {
|
||||
if self.Verbose {
|
||||
@@ -422,7 +422,11 @@ func (self *TPCSVImporter) importActionTriggers(fn string) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
weight, err := strconv.ParseFloat(record[7], 64)
|
||||
recurrent, err := strconv.ParseBool(record[5])
|
||||
if err != nil {
|
||||
log.Printf("Ignoring line %d, warning: <%s>", lineNr, err.Error())
|
||||
}
|
||||
weight, err := strconv.ParseFloat(record[8], 64)
|
||||
if err != nil {
|
||||
if self.Verbose {
|
||||
log.Printf("Ignoring line %d, warning: <%s> ", lineNr, err.Error())
|
||||
@@ -434,6 +438,7 @@ func (self *TPCSVImporter) importActionTriggers(fn string) error {
|
||||
Direction: direction,
|
||||
ThresholdType: thresholdType,
|
||||
ThresholdValue: threshold,
|
||||
Recurrent: recurrent,
|
||||
DestinationId: destinationTag,
|
||||
Weight: weight,
|
||||
ActionsId: actionsTag,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -46,10 +46,10 @@ type Mediator struct {
|
||||
}
|
||||
|
||||
// Retrive the cost from logging database
|
||||
func (self *Mediator) getCostsFromDB(cgrid string) (cc *engine.CallCost, err error) {
|
||||
func (self *Mediator) getCostsFromDB(cgrid, runId string) (cc *engine.CallCost, err error) {
|
||||
for i := 0; i < 3; i++ { // Mechanism to avoid concurrency between SessionManager writing the costs and mediator picking them up
|
||||
cc, err = self.logDb.GetCallCostLog(cgrid, engine.SESSION_MANAGER_SOURCE, utils.DEFAULT_RUNID) //ToDo: What are we getting when there is no log?
|
||||
if cc != nil { // There were no errors, chances are that we got what we are looking for
|
||||
cc, err = self.logDb.GetCallCostLog(cgrid, engine.SESSION_MANAGER_SOURCE, runId) //ToDo: What are we getting when there is no log?
|
||||
if cc != nil { // There were no errors, chances are that we got what we are looking for
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(i) * time.Second)
|
||||
@@ -58,88 +58,86 @@ func (self *Mediator) getCostsFromDB(cgrid string) (cc *engine.CallCost, err err
|
||||
}
|
||||
|
||||
// Retrive the cost from engine
|
||||
func (self *Mediator) getCostsFromRater(cdr *utils.StoredCdr) (*engine.CallCost, error) {
|
||||
func (self *Mediator) getCostsFromRater(storedCdr *utils.StoredCdr) (*engine.CallCost, error) {
|
||||
cc := &engine.CallCost{}
|
||||
var err error
|
||||
if cdr.Duration == time.Duration(0) { // failed call, returning empty callcost, no error
|
||||
if storedCdr.Duration == time.Duration(0) { // failed call, returning empty callcost, no error
|
||||
return cc, nil
|
||||
}
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: "*out", //record[m.directionFields[runIdx]] TODO: fix me
|
||||
Tenant: cdr.Tenant,
|
||||
Category: cdr.Category,
|
||||
Subject: cdr.Subject,
|
||||
Account: cdr.Account,
|
||||
Destination: cdr.Destination,
|
||||
TimeStart: cdr.AnswerTime,
|
||||
TimeEnd: cdr.AnswerTime.Add(cdr.Duration),
|
||||
Tenant: storedCdr.Tenant,
|
||||
Category: storedCdr.Category,
|
||||
Subject: storedCdr.Subject,
|
||||
Account: storedCdr.Account,
|
||||
Destination: storedCdr.Destination,
|
||||
TimeStart: storedCdr.AnswerTime,
|
||||
TimeEnd: storedCdr.AnswerTime.Add(storedCdr.Duration),
|
||||
LoopIndex: 0,
|
||||
DurationIndex: cdr.Duration,
|
||||
DurationIndex: storedCdr.Duration,
|
||||
}
|
||||
if cdr.ReqType == utils.PSEUDOPREPAID {
|
||||
if storedCdr.ReqType == utils.PSEUDOPREPAID {
|
||||
err = self.connector.Debit(cd, cc)
|
||||
} else {
|
||||
err = self.connector.GetCost(cd, cc)
|
||||
}
|
||||
if err != nil {
|
||||
self.logDb.LogError(cdr.CgrId, engine.MEDIATOR_SOURCE, cdr.MediationRunId, err.Error())
|
||||
self.logDb.LogError(storedCdr.CgrId, engine.MEDIATOR_SOURCE, storedCdr.MediationRunId, err.Error())
|
||||
} else {
|
||||
// If the mediator calculated a price it will write it to logdb
|
||||
self.logDb.LogCallCost(utils.Sha1(cdr.AccId, cdr.SetupTime.String()), engine.MEDIATOR_SOURCE, cdr.MediationRunId, cc)
|
||||
self.logDb.LogCallCost(storedCdr.CgrId, engine.MEDIATOR_SOURCE, storedCdr.MediationRunId, cc)
|
||||
}
|
||||
return cc, err
|
||||
}
|
||||
|
||||
func (self *Mediator) rateCDR(cdr *utils.StoredCdr) error {
|
||||
func (self *Mediator) rateCDR(storedCdr *utils.StoredCdr) error {
|
||||
var qryCC *engine.CallCost
|
||||
var errCost error
|
||||
if cdr.ReqType == utils.PREPAID || cdr.ReqType == utils.POSTPAID {
|
||||
if storedCdr.ReqType == utils.PREPAID || storedCdr.ReqType == utils.POSTPAID {
|
||||
// Should be previously calculated and stored in DB
|
||||
qryCC, errCost = self.getCostsFromDB(cdr.CgrId)
|
||||
qryCC, errCost = self.getCostsFromDB(storedCdr.CgrId, storedCdr.MediationRunId)
|
||||
} else {
|
||||
qryCC, errCost = self.getCostsFromRater(cdr)
|
||||
qryCC, errCost = self.getCostsFromRater(storedCdr)
|
||||
}
|
||||
if errCost != nil {
|
||||
return errCost
|
||||
} else if qryCC == nil {
|
||||
return errors.New("No cost returned from rater")
|
||||
}
|
||||
cdr.Cost = qryCC.Cost
|
||||
storedCdr.Cost = qryCC.Cost
|
||||
return nil
|
||||
}
|
||||
|
||||
// Forks original CDR based on original request plus runIds for extra mediation
|
||||
func (self *Mediator) RateCdr(dbcdr utils.RawCDR) error {
|
||||
rtCdr, err := utils.NewStoredCdrFromRawCDR(dbcdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cdrs := []*utils.StoredCdr{rtCdr} // Start with initial dbcdr, will add here all to be mediated
|
||||
attrsDC := utils.AttrDerivedChargers{Tenant: rtCdr.Tenant, Category: rtCdr.Category, Direction: rtCdr.Direction,
|
||||
Account: rtCdr.Account, Subject: rtCdr.Subject}
|
||||
func (self *Mediator) RateCdr(storedCdr *utils.StoredCdr) error {
|
||||
cdrs := []*utils.StoredCdr{storedCdr} // Start with initial storCdr, will add here all to be mediated
|
||||
attrsDC := utils.AttrDerivedChargers{Tenant: storedCdr.Tenant, Category: storedCdr.Category, Direction: storedCdr.Direction,
|
||||
Account: storedCdr.Account, Subject: storedCdr.Subject}
|
||||
var dcs utils.DerivedChargers
|
||||
if err := self.connector.GetDerivedChargers(attrsDC, &dcs); err != nil {
|
||||
errText := fmt.Sprintf("Could not get derived charging for cgrid %s, error: %s", rtCdr.CgrId, err.Error())
|
||||
errText := fmt.Sprintf("Could not get derived charging for cgrid %s, error: %s", storedCdr.CgrId, err.Error())
|
||||
engine.Logger.Err(errText)
|
||||
return errors.New(errText)
|
||||
}
|
||||
for _, dc := range dcs {
|
||||
forkedCdr, err := dbcdr.ForkCdr(dc.RunId, dc.ReqTypeField, dc.DirectionField,
|
||||
dc.TenantField, dc.CategoryField, dc.AccountField, dc.SubjectField, dc.DestinationField, dc.SetupTimeField, dc.AnswerTimeField, dc.DurationField, []string{}, true)
|
||||
forkedCdr, err := storedCdr.ForkCdr(dc.RunId, &utils.RSRField{Id: dc.ReqTypeField}, &utils.RSRField{Id: dc.DirectionField},
|
||||
&utils.RSRField{Id: dc.TenantField}, &utils.RSRField{Id: dc.CategoryField}, &utils.RSRField{Id: dc.AccountField},
|
||||
&utils.RSRField{Id: dc.SubjectField}, &utils.RSRField{Id: dc.DestinationField}, &utils.RSRField{Id: dc.SetupTimeField},
|
||||
&utils.RSRField{Id: dc.AnswerTimeField}, &utils.RSRField{Id: dc.DurationField}, []*utils.RSRField{}, true)
|
||||
engine.Logger.Debug(fmt.Sprintf("Forked CDR for dc: %v, is: %v", dc, forkedCdr))
|
||||
if err != nil { // Errors on fork, cannot calculate further, write that into db for later analysis
|
||||
self.cdrDb.SetRatedCdr(&utils.StoredCdr{CgrId: dbcdr.GetCgrId(), MediationRunId: dc.RunId, Cost: -1.0}, err.Error()) // Cannot fork CDR, important just runid and error
|
||||
self.cdrDb.SetRatedCdr(&utils.StoredCdr{CgrId: storedCdr.CgrId, CdrSource: utils.FORKED_CDR, MediationRunId: dc.RunId, Cost: -1},
|
||||
err.Error()) // Cannot fork CDR, important just runid and error
|
||||
continue
|
||||
}
|
||||
cdrs = append(cdrs, forkedCdr)
|
||||
}
|
||||
for _, cdr := range cdrs {
|
||||
extraInfo := ""
|
||||
if err = self.rateCDR(cdr); err != nil {
|
||||
if err := self.rateCDR(cdr); err != nil {
|
||||
extraInfo = err.Error()
|
||||
}
|
||||
if err := self.cdrDb.SetRatedCdr(cdr, extraInfo); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Mediator> Could not record cost for cgrid: <%s>, err: <%s>, cost: %f, extraInfo: %s",
|
||||
engine.Logger.Err(fmt.Sprintf("<Mediator> Could not record cost for cgrid: <%s>, ERROR: <%s>, cost: %f, extraInfo: %s",
|
||||
cdr.CgrId, err.Error(), cdr.Cost, extraInfo))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ func TestInjectCdrs(t *testing.T) {
|
||||
"tenant": "cgrates.org", "tor": "call", "account": "dan", "subject": "dan", "destination": "+4986517173964",
|
||||
"answer_time": "2013-11-07T09:42:26Z", "duration": "20"}
|
||||
for _, cdr := range []utils.CgrCdr{cgrCdr1, cgrCdr2} {
|
||||
if err := cdrStor.SetCdr(cdr); err != nil {
|
||||
if err := cdrStor.SetCdr(cdr.AsStoredCdr()); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
153
utils/cgrcdr.go
153
utils/cgrcdr.go
@@ -19,10 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -78,7 +75,7 @@ func (cgrCdr CgrCdr) GetDestination() string {
|
||||
}
|
||||
|
||||
func (cgrCdr CgrCdr) GetCategory() string {
|
||||
return cgrCdr[Category]
|
||||
return cgrCdr[CATEGORY]
|
||||
}
|
||||
|
||||
func (cgrCdr CgrCdr) GetTenant() string {
|
||||
@@ -108,133 +105,23 @@ func (cgrCdr CgrCdr) GetDuration() (time.Duration, error) {
|
||||
return ParseDurationWithSecs(cgrCdr[DURATION])
|
||||
}
|
||||
|
||||
// Used in mediation, fieldsMandatory marks whether missing field out of request represents error or can be ignored
|
||||
// If the fields in parameters start with ^ their value is considered instead of dynamically retrieving it from CDR
|
||||
func (cgrCdr CgrCdr) ForkCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) {
|
||||
if IsSliceMember([]string{runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld}, "") {
|
||||
return nil, errors.New(fmt.Sprintf("%s:FieldName", ERR_MANDATORY_IE_MISSING)) // All input field names are mandatory
|
||||
}
|
||||
var err error
|
||||
var hasKey bool
|
||||
var sTimeStr, aTimeStr, durStr string
|
||||
rtCdr := new(StoredCdr)
|
||||
rtCdr.MediationRunId = runId
|
||||
rtCdr.Cost = -1.0 // Default for non-rated CDR
|
||||
if rtCdr.AccId, hasKey = cgrCdr[ACCID]; !hasKey {
|
||||
if fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, ACCID))
|
||||
}
|
||||
}
|
||||
// MetaDefault will automatically be converted to their standard values
|
||||
if reqTypeFld == META_DEFAULT {
|
||||
reqTypeFld = REQTYPE
|
||||
}
|
||||
if directionFld == META_DEFAULT {
|
||||
directionFld = DIRECTION
|
||||
}
|
||||
if tenantFld == META_DEFAULT {
|
||||
tenantFld = TENANT
|
||||
}
|
||||
if torFld == META_DEFAULT {
|
||||
torFld = Category
|
||||
}
|
||||
if accountFld == META_DEFAULT {
|
||||
accountFld = ACCOUNT
|
||||
}
|
||||
if subjectFld == META_DEFAULT {
|
||||
subjectFld = SUBJECT
|
||||
}
|
||||
if destFld == META_DEFAULT {
|
||||
destFld = DESTINATION
|
||||
}
|
||||
if setupTimeFld == META_DEFAULT {
|
||||
setupTimeFld = SETUP_TIME
|
||||
}
|
||||
if answerTimeFld == META_DEFAULT {
|
||||
answerTimeFld = ANSWER_TIME
|
||||
}
|
||||
if durationFld == META_DEFAULT {
|
||||
durationFld = DURATION
|
||||
}
|
||||
if rtCdr.CdrHost, hasKey = cgrCdr[CDRHOST]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, CDRHOST))
|
||||
}
|
||||
if rtCdr.CdrSource, hasKey = cgrCdr[CDRSOURCE]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, CDRSOURCE))
|
||||
}
|
||||
if strings.HasPrefix(reqTypeFld, STATIC_VALUE_PREFIX) { // Values starting with prefix are not dynamically populated
|
||||
rtCdr.ReqType = reqTypeFld[1:]
|
||||
} else if rtCdr.ReqType, hasKey = cgrCdr[reqTypeFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, reqTypeFld))
|
||||
}
|
||||
if strings.HasPrefix(directionFld, STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Direction = directionFld[1:]
|
||||
} else if rtCdr.Direction, hasKey = cgrCdr[directionFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, directionFld))
|
||||
}
|
||||
if strings.HasPrefix(tenantFld, STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Tenant = tenantFld[1:]
|
||||
} else if rtCdr.Tenant, hasKey = cgrCdr[tenantFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, tenantFld))
|
||||
}
|
||||
if strings.HasPrefix(torFld, STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Category = torFld[1:]
|
||||
} else if rtCdr.Category, hasKey = cgrCdr[torFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, torFld))
|
||||
}
|
||||
if strings.HasPrefix(accountFld, STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Account = accountFld[1:]
|
||||
} else if rtCdr.Account, hasKey = cgrCdr[accountFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, accountFld))
|
||||
}
|
||||
if strings.HasPrefix(subjectFld, STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Subject = subjectFld[1:]
|
||||
} else if rtCdr.Subject, hasKey = cgrCdr[subjectFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, subjectFld))
|
||||
}
|
||||
if strings.HasPrefix(destFld, STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Destination = destFld[1:]
|
||||
} else if rtCdr.Destination, hasKey = cgrCdr[destFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, destFld))
|
||||
}
|
||||
if sTimeStr, hasKey = cgrCdr[setupTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(setupTimeFld, STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, setupTimeFld))
|
||||
} else {
|
||||
if strings.HasPrefix(setupTimeFld, STATIC_VALUE_PREFIX) {
|
||||
sTimeStr = setupTimeFld[1:]
|
||||
}
|
||||
if rtCdr.SetupTime, err = ParseTimeDetectLayout(sTimeStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if aTimeStr, hasKey = cgrCdr[answerTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(answerTimeFld, STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, answerTimeFld))
|
||||
} else {
|
||||
if strings.HasPrefix(answerTimeFld, STATIC_VALUE_PREFIX) {
|
||||
aTimeStr = answerTimeFld[1:]
|
||||
}
|
||||
if rtCdr.AnswerTime, err = ParseTimeDetectLayout(aTimeStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if durStr, hasKey = cgrCdr[durationFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(durationFld, STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, durationFld))
|
||||
} else {
|
||||
if strings.HasPrefix(durationFld, STATIC_VALUE_PREFIX) {
|
||||
durStr = durationFld[1:]
|
||||
}
|
||||
if rtCdr.Duration, err = ParseDurationWithSecs(durStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rtCdr.ExtraFields = make(map[string]string, len(extraFlds))
|
||||
for _, fldName := range extraFlds {
|
||||
if fldVal, hasKey := cgrCdr[fldName]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, fldName))
|
||||
} else {
|
||||
rtCdr.ExtraFields[fldName] = fldVal
|
||||
}
|
||||
}
|
||||
rtCdr.CgrId = Sha1(rtCdr.AccId, rtCdr.SetupTime.String())
|
||||
return rtCdr, nil
|
||||
func (cgrCdr CgrCdr) AsStoredCdr() *StoredCdr {
|
||||
storCdr := new(StoredCdr)
|
||||
storCdr.CgrId = cgrCdr.GetCgrId()
|
||||
storCdr.AccId = cgrCdr.GetAccId()
|
||||
storCdr.CdrHost = cgrCdr.GetCdrHost()
|
||||
storCdr.CdrSource = cgrCdr.GetCdrSource()
|
||||
storCdr.ReqType = cgrCdr.GetReqType()
|
||||
storCdr.Direction = cgrCdr.GetDirection()
|
||||
storCdr.Tenant = cgrCdr.GetTenant()
|
||||
storCdr.Category = cgrCdr.GetCategory()
|
||||
storCdr.Account = cgrCdr.GetAccount()
|
||||
storCdr.Subject = cgrCdr.GetSubject()
|
||||
storCdr.Destination = cgrCdr.GetDestination()
|
||||
storCdr.SetupTime, _ = cgrCdr.GetSetupTime() // Not interested to process errors, should do them if necessary in a previous step
|
||||
storCdr.AnswerTime, _ = cgrCdr.GetAnswerTime()
|
||||
storCdr.Duration, _ = cgrCdr.GetDuration()
|
||||
storCdr.ExtraFields = cgrCdr.GetExtraFields()
|
||||
storCdr.Cost = -1
|
||||
return storCdr
|
||||
}
|
||||
|
||||
@@ -28,9 +28,13 @@ import (
|
||||
curl --data "accid=asbfdsaf&cdrhost=192.168.1.1&reqtype=rated&direction=*out&tenant=cgrates.org&tor=call&account=1001&subject=1001&destination=1002&time_answer=1383813746&duration=10&field_extr1=val_extr1&fieldextr2=valextr2" http://ipbxdev:2080/cgr
|
||||
*/
|
||||
|
||||
func TestCgrCdrInterfaces(t *testing.T) {
|
||||
var _ RawCdr = make(CgrCdr)
|
||||
}
|
||||
|
||||
func TestCgrCdrFields(t *testing.T) {
|
||||
cgrCdr := CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call",
|
||||
"account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:20Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10",
|
||||
cgrCdr := CgrCdr{ACCID: "dsafdsaf", CDRHOST: "192.168.1.1", REQTYPE: "rated", DIRECTION: "*out", TENANT: "cgrates.org", CATEGORY: "call",
|
||||
ACCOUNT: "1001", SUBJECT: "1001", DESTINATION: "1002", SETUP_TIME: "2013-11-07T08:42:20Z", ANSWER_TIME: "2013-11-07T08:42:26Z", DURATION: "10",
|
||||
"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
|
||||
setupTime, _ := ParseTimeDetectLayout("2013-11-07T08:42:20Z")
|
||||
if cgrCdr.GetCgrId() != Sha1("dsafdsaf", setupTime.String()) {
|
||||
@@ -85,76 +89,16 @@ func TestCgrCdrFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgrCdrForkCdr(t *testing.T) {
|
||||
sampleCdr1 := &CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "cdrsource": "source_test", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call",
|
||||
"account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:24Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10",
|
||||
func TestCgrCdrAsStoredCdr(t *testing.T) {
|
||||
cgrCdr := CgrCdr{ACCID: "dsafdsaf", CDRHOST: "192.168.1.1", CDRSOURCE: "internal_test", REQTYPE: "rated", DIRECTION: "*out", TENANT: "cgrates.org", CATEGORY: "call",
|
||||
ACCOUNT: "1001", SUBJECT: "1001", DESTINATION: "1002", SETUP_TIME: "2013-11-07T08:42:20Z", ANSWER_TIME: "2013-11-07T08:42:26Z", DURATION: "10",
|
||||
"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
|
||||
rtSampleCdrOut, err := sampleCdr1.ForkCdr("sample_run1", "reqtype", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration",
|
||||
[]string{}, true)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error received", err)
|
||||
}
|
||||
setupTime1 := time.Date(2013, 11, 7, 8, 42, 24, 0, time.UTC)
|
||||
expctSplRatedCdr := &StoredCdr{CgrId: Sha1("dsafdsaf", setupTime1.String()), AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "source_test", ReqType: "rated",
|
||||
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: setupTime1, AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Duration: 10000000000, ExtraFields: map[string]string{}, MediationRunId: "sample_run1", Cost: -1}
|
||||
if !reflect.DeepEqual(expctSplRatedCdr, rtSampleCdrOut) {
|
||||
t.Errorf("Expected: %v, received: %v", expctSplRatedCdr, rtSampleCdrOut)
|
||||
}
|
||||
cgrCdr := &CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "cdrsource": "source_test", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call",
|
||||
"account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:24Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10",
|
||||
"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
|
||||
rtCdrOut, err := cgrCdr.ForkCdr("wholesale_run", "reqtype", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration",
|
||||
[]string{"field_extr1", "fieldextr2"}, true)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error received", err)
|
||||
}
|
||||
setupTime, _ := ParseTimeDetectLayout("2013-11-07T08:42:24Z")
|
||||
expctRatedCdr := &StoredCdr{CgrId: Sha1("dsafdsaf", setupTime.String()), AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "source_test", ReqType: "rated",
|
||||
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Unix(1383813744, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Duration: 10000000000, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, MediationRunId: "wholesale_run", Cost: -1}
|
||||
if !reflect.DeepEqual(rtCdrOut, expctRatedCdr) {
|
||||
t.Errorf("Received: %v, expected: %v", rtCdrOut, expctRatedCdr)
|
||||
}
|
||||
rtCdrOut2, err := cgrCdr.ForkCdr("wholesale_run", "^postpaid", "^*in", "^cgrates.com", "^premium_call", "^first_account", "^first_subject", "destination",
|
||||
"^2013-12-07T08:42:24Z", "^2013-12-07T08:42:26Z", "^12s", []string{"field_extr1", "fieldextr2"}, true)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error received", err)
|
||||
}
|
||||
expctRatedCdr2 := &StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "source_test", ReqType: "postpaid",
|
||||
Direction: "*in", Tenant: "cgrates.com", Category: "premium_call", Account: "first_account", Subject: "first_subject", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC),
|
||||
AnswerTime: time.Date(2013, 12, 7, 8, 42, 26, 0, time.UTC), Duration: time.Duration(12) * time.Second,
|
||||
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, MediationRunId: "wholesale_run", Cost: -1}
|
||||
if !reflect.DeepEqual(rtCdrOut2, expctRatedCdr2) {
|
||||
t.Errorf("Received: %v, expected: %v", rtCdrOut2, expctRatedCdr2)
|
||||
}
|
||||
_, err = cgrCdr.ForkCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration",
|
||||
[]string{"field_extr1", "fieldextr2"}, true)
|
||||
if err == nil {
|
||||
t.Error("Failed to detect missing header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgrCdrForkCdrFromMetaDefaults(t *testing.T) {
|
||||
cgrCdr := &CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "cdrsource": "source_test", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call",
|
||||
"account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:24Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10",
|
||||
"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
|
||||
setupTime := time.Date(2013, 11, 7, 8, 42, 24, 0, time.UTC)
|
||||
expctCdr := &StoredCdr{CgrId: Sha1("dsafdsaf", setupTime.String()), AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "source_test", ReqType: "rated",
|
||||
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: setupTime, AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
|
||||
Duration: time.Duration(10) * time.Second,
|
||||
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, MediationRunId: "wholesale_run", Cost: -1}
|
||||
cdrOut, err := cgrCdr.ForkCdr("wholesale_run", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT,
|
||||
META_DEFAULT, META_DEFAULT, META_DEFAULT, []string{"field_extr1", "fieldextr2"}, true)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error received", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expctCdr, cdrOut) {
|
||||
t.Errorf("Expected: %v, received: %v", expctCdr, cdrOut)
|
||||
setupTime, _ := ParseTimeDetectLayout(cgrCdr["setup_time"])
|
||||
expctRtCdr := &StoredCdr{CgrId: Sha1(cgrCdr["accid"], setupTime.String()), AccId: cgrCdr["accid"], CdrHost: cgrCdr["cdrhost"], CdrSource: cgrCdr["cdrsource"], ReqType: cgrCdr["reqtype"],
|
||||
Direction: cgrCdr[DIRECTION], Tenant: cgrCdr["tenant"], Category: cgrCdr[CATEGORY], Account: cgrCdr["account"], Subject: cgrCdr["subject"],
|
||||
Destination: cgrCdr["destination"], SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), Duration: time.Duration(10) * time.Second,
|
||||
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: -1}
|
||||
if storedCdr := cgrCdr.AsStoredCdr(); !reflect.DeepEqual(expctRtCdr, storedCdr) {
|
||||
t.Errorf("Expecting %v, received: %v", expctRtCdr, storedCdr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ const (
|
||||
REQTYPE = "reqtype"
|
||||
DIRECTION = "direction"
|
||||
TENANT = "tenant"
|
||||
Category = "tor"
|
||||
CATEGORY = "category"
|
||||
ACCOUNT = "account"
|
||||
SUBJECT = "subject"
|
||||
DESTINATION = "destination"
|
||||
@@ -92,7 +92,7 @@ const (
|
||||
COST = "cost"
|
||||
DEFAULT_RUNID = "default"
|
||||
STATIC_VALUE_PREFIX = "^"
|
||||
CDRE_CSV = "csv"
|
||||
CSV = "csv"
|
||||
CDRE_DRYRUN = "dry_run"
|
||||
INTERNAL = "internal"
|
||||
ZERO_RATING_SUBJECT_PREFIX = "*zero"
|
||||
@@ -104,8 +104,11 @@ const (
|
||||
CONCATENATED_KEY_SEP = ":"
|
||||
META_DEFAULT = "*default"
|
||||
FORKED_CDR = "forked_cdr"
|
||||
UNIT_TEST = "UNIT_TEST"
|
||||
HDR_VAL_SEP = "/"
|
||||
)
|
||||
|
||||
var (
|
||||
CdreCdrFormats = []string{CDRE_CSV, CDRE_DRYRUN, CDRE_FIXED_WIDTH}
|
||||
CdreCdrFormats = []string{CSV, CDRE_DRYRUN, CDRE_FIXED_WIDTH}
|
||||
PrimaryCdrFields = []string{ACCID, CDRHOST, CDRSOURCE, REQTYPE, DIRECTION, TENANT, CATEGORY, ACCOUNT, SUBJECT, DESTINATION, SETUP_TIME, ANSWER_TIME, DURATION}
|
||||
)
|
||||
|
||||
@@ -18,28 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var PrimaryCdrFields []string = []string{ACCID, CDRHOST, CDRSOURCE, REQTYPE, DIRECTION, TENANT, Category, ACCOUNT, SUBJECT, DESTINATION, SETUP_TIME, ANSWER_TIME, DURATION}
|
||||
|
||||
// RawCDR is the type containing all the original CDR fields, needs it as it is for later usage
|
||||
type RawCDR interface {
|
||||
GetCgrId() string
|
||||
GetAccId() string
|
||||
GetCdrHost() string
|
||||
GetCdrSource() string
|
||||
GetDirection() string
|
||||
GetSubject() string
|
||||
GetAccount() string
|
||||
GetDestination() string
|
||||
GetCategory() string
|
||||
GetTenant() string
|
||||
GetReqType() string
|
||||
GetSetupTime() (time.Time, error) // Time when the call was set-up
|
||||
GetAnswerTime() (time.Time, error) // Time when the call was answered
|
||||
GetDuration() (time.Duration, error)
|
||||
GetExtraFields() map[string]string //Stores extra CDR Fields
|
||||
ForkCdr(string, string, string, string, string, string, string, string, string, string, string, []string, bool) (*StoredCdr, error) // Based on fields queried will return a particular instance of RatedCDR
|
||||
// RawCDR is the original CDR received from external sources (eg: FreeSWITCH)
|
||||
type RawCdr interface {
|
||||
AsStoredCdr() *StoredCdr // Convert the inbound Cdr into internally used one, CgrCdr
|
||||
}
|
||||
|
||||
@@ -28,6 +28,15 @@ func NewRSRField(fldStr string) (*RSRField, error) {
|
||||
if len(fldStr) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
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, HDR_VAL_SEP); len(splt) == 2 { // Using | as separator since ':' is often use in date/time fields
|
||||
staticHdr, staticVal = splt[0][1:], splt[1]
|
||||
} else {
|
||||
staticHdr, staticVal = splt[0][1:], splt[0][1:] // If no split, header will remain as original, value as header without the prefix
|
||||
}
|
||||
return &RSRField{Id: staticHdr, staticValue: staticVal}, nil
|
||||
}
|
||||
if !strings.HasPrefix(fldStr, REGEXP_PREFIX) {
|
||||
return &RSRField{Id: fldStr}, nil
|
||||
}
|
||||
@@ -53,12 +62,16 @@ func NewRSRField(fldStr string) (*RSRField, error) {
|
||||
}
|
||||
|
||||
type RSRField struct {
|
||||
Id string // Identifier
|
||||
RSRules []*ReSearchReplace // Rules to use when processing field value
|
||||
Id string // Identifier
|
||||
RSRules []*ReSearchReplace // Rules to use when processing field value
|
||||
staticValue string // If defined, enforces parsing always to this value
|
||||
}
|
||||
|
||||
// Parse the field value from a string
|
||||
func (rsrf *RSRField) ParseValue(value string) string {
|
||||
if len(rsrf.staticValue) != 0 { // Enforce parsing of static values
|
||||
return rsrf.staticValue
|
||||
}
|
||||
if len(value) == 0 {
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -80,3 +80,20 @@ func TestConvertPlusNationalAnd00(t *testing.T) {
|
||||
t.Errorf("Expecting: 003186517174963, received: %s", parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRParseStatic(t *testing.T) {
|
||||
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)
|
||||
} else if parsed := rsrField.ParseValue("dynamic_value"); parsed != "static_value" {
|
||||
t.Errorf("Expected: %s, received: %s", "static_value", parsed)
|
||||
}
|
||||
if rsrField, err := NewRSRField(`^static_hdrvalue`); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, &RSRField{Id: "static_hdrvalue", staticValue: "static_hdrvalue"}) {
|
||||
t.Errorf("Unexpected RSRField received: %v", rsrField)
|
||||
} else if parsed := rsrField.ParseValue("dynamic_value"); parsed != "static_hdrvalue" {
|
||||
t.Errorf("Expected: %s, received: %s", "static_hdrvalue", parsed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,40 +19,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewStoredCdrFromRawCDR(rawcdr RawCDR) (*StoredCdr, error) {
|
||||
var err error
|
||||
strCdr := new(StoredCdr)
|
||||
strCdr.CgrId = rawcdr.GetCgrId()
|
||||
strCdr.AccId = rawcdr.GetAccId()
|
||||
strCdr.CdrHost = rawcdr.GetCdrHost()
|
||||
strCdr.CdrSource = rawcdr.GetCdrSource()
|
||||
strCdr.ReqType = rawcdr.GetReqType()
|
||||
strCdr.Direction = rawcdr.GetDirection()
|
||||
strCdr.Tenant = rawcdr.GetTenant()
|
||||
strCdr.Category = rawcdr.GetCategory()
|
||||
strCdr.Account = rawcdr.GetAccount()
|
||||
strCdr.Subject = rawcdr.GetSubject()
|
||||
strCdr.Destination = rawcdr.GetDestination()
|
||||
if strCdr.SetupTime, err = rawcdr.GetSetupTime(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strCdr.AnswerTime, err = rawcdr.GetAnswerTime(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
strCdr.Duration, _ = rawcdr.GetDuration()
|
||||
strCdr.ExtraFields = rawcdr.GetExtraFields()
|
||||
strCdr.MediationRunId = DEFAULT_RUNID
|
||||
strCdr.Cost = -1
|
||||
return strCdr, nil
|
||||
}
|
||||
|
||||
// Rated CDR as extracted from StorDb. Kinda standard of internal CDR, complies to CDR interface also
|
||||
// Kinda standard of internal CDR, complies to CDR interface also
|
||||
type StoredCdr struct {
|
||||
CgrId string
|
||||
OrderId int64 // Stor order id used as export order id
|
||||
@@ -74,68 +49,6 @@ type StoredCdr struct {
|
||||
Cost float64
|
||||
}
|
||||
|
||||
// Methods maintaining RawCDR interface
|
||||
|
||||
func (storedCdr *StoredCdr) GetCgrId() string {
|
||||
return storedCdr.CgrId
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetAccId() string {
|
||||
return storedCdr.AccId
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetCdrHost() string {
|
||||
return storedCdr.CdrHost
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetCdrSource() string {
|
||||
return storedCdr.CdrSource
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetDirection() string {
|
||||
return storedCdr.Direction
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetSubject() string {
|
||||
return storedCdr.Subject
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetAccount() string {
|
||||
return storedCdr.Account
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetDestination() string {
|
||||
return storedCdr.Destination
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetCategory() string {
|
||||
return storedCdr.Category
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetTenant() string {
|
||||
return storedCdr.Tenant
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetReqType() string {
|
||||
return storedCdr.ReqType
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetSetupTime() (time.Time, error) {
|
||||
return storedCdr.SetupTime, nil
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetAnswerTime() (time.Time, error) {
|
||||
return storedCdr.AnswerTime, nil
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetDuration() (time.Duration, error) {
|
||||
return storedCdr.Duration, nil
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) GetExtraFields() map[string]string {
|
||||
return storedCdr.ExtraFields
|
||||
}
|
||||
|
||||
// Return cost as string, formated with number of decimals configured
|
||||
func (storedCdr *StoredCdr) FormatCost(shiftDecimals, roundDecimals int) string {
|
||||
cost := storedCdr.Cost
|
||||
@@ -145,81 +58,165 @@ func (storedCdr *StoredCdr) FormatCost(shiftDecimals, roundDecimals int) string
|
||||
return strconv.FormatFloat(cost, 'f', roundDecimals, 64)
|
||||
}
|
||||
|
||||
// Converts part of the rated Cdr as httpForm used to post remotely to CDRS
|
||||
func (storedCdr *StoredCdr) AsRawCdrHttpForm() url.Values {
|
||||
// Used to retrieve fields as string, primary fields are const labeled
|
||||
func (storedCdr *StoredCdr) FieldAsString(rsrFld *RSRField) string {
|
||||
switch rsrFld.Id {
|
||||
case CGRID:
|
||||
return rsrFld.ParseValue(storedCdr.CgrId)
|
||||
case ORDERID:
|
||||
return rsrFld.ParseValue(strconv.FormatInt(storedCdr.OrderId, 10))
|
||||
case ACCID:
|
||||
return rsrFld.ParseValue(storedCdr.AccId)
|
||||
case CDRHOST:
|
||||
return rsrFld.ParseValue(storedCdr.CdrHost)
|
||||
case CDRSOURCE:
|
||||
return rsrFld.ParseValue(storedCdr.CdrSource)
|
||||
case REQTYPE:
|
||||
return rsrFld.ParseValue(storedCdr.ReqType)
|
||||
case DIRECTION:
|
||||
return rsrFld.ParseValue(storedCdr.Direction)
|
||||
case TENANT:
|
||||
return rsrFld.ParseValue(storedCdr.Tenant)
|
||||
case CATEGORY:
|
||||
return rsrFld.ParseValue(storedCdr.Category)
|
||||
case ACCOUNT:
|
||||
return rsrFld.ParseValue(storedCdr.Account)
|
||||
case SUBJECT:
|
||||
return rsrFld.ParseValue(storedCdr.Subject)
|
||||
case DESTINATION:
|
||||
return rsrFld.ParseValue(storedCdr.Destination)
|
||||
case SETUP_TIME:
|
||||
return rsrFld.ParseValue(storedCdr.SetupTime.String())
|
||||
case ANSWER_TIME:
|
||||
return rsrFld.ParseValue(storedCdr.AnswerTime.String())
|
||||
case DURATION:
|
||||
return rsrFld.ParseValue(strconv.FormatFloat(storedCdr.Duration.Seconds(), 'f', -1, 64))
|
||||
case MEDI_RUNID:
|
||||
return rsrFld.ParseValue(storedCdr.MediationRunId)
|
||||
case COST:
|
||||
return rsrFld.ParseValue(strconv.FormatFloat(storedCdr.Cost, 'f', -1, 64)) // Recommended to use FormatCost
|
||||
default:
|
||||
return rsrFld.ParseValue(storedCdr.ExtraFields[rsrFld.Id])
|
||||
}
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) AsStoredCdr() *StoredCdr {
|
||||
return storedCdr
|
||||
}
|
||||
|
||||
// Ability to send the CgrCdr remotely to another CDR server
|
||||
func (storedCdr *StoredCdr) AsHttpForm() url.Values {
|
||||
v := url.Values{}
|
||||
for fld, val := range storedCdr.ExtraFields {
|
||||
v.Set(fld, val)
|
||||
}
|
||||
v.Set(ACCID, storedCdr.AccId)
|
||||
v.Set(CDRHOST, storedCdr.CdrHost)
|
||||
v.Set(CDRSOURCE, storedCdr.CdrSource)
|
||||
v.Set(REQTYPE, storedCdr.ReqType)
|
||||
v.Set(DIRECTION, storedCdr.Direction)
|
||||
v.Set(TENANT, storedCdr.Tenant)
|
||||
v.Set(Category, storedCdr.Category)
|
||||
v.Set(CATEGORY, storedCdr.Category)
|
||||
v.Set(ACCOUNT, storedCdr.Account)
|
||||
v.Set(SUBJECT, storedCdr.Subject)
|
||||
v.Set(DESTINATION, storedCdr.Destination)
|
||||
v.Set(SETUP_TIME, storedCdr.SetupTime.String())
|
||||
v.Set(ANSWER_TIME, storedCdr.AnswerTime.String())
|
||||
v.Set(DURATION, strconv.FormatFloat(storedCdr.Duration.Seconds(), 'f', -1, 64))
|
||||
for fld, val := range storedCdr.ExtraFields {
|
||||
v.Set(fld, val)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Used to export fields as string, primary fields are const labeled
|
||||
func (storedCdr *StoredCdr) ExportFieldValue(fldName string) string {
|
||||
switch fldName {
|
||||
case CGRID:
|
||||
return storedCdr.CgrId
|
||||
case ORDERID:
|
||||
return strconv.FormatInt(storedCdr.OrderId, 10)
|
||||
case ACCID:
|
||||
return storedCdr.AccId
|
||||
case CDRHOST:
|
||||
return storedCdr.CdrHost
|
||||
case CDRSOURCE:
|
||||
return storedCdr.CdrSource
|
||||
case REQTYPE:
|
||||
return storedCdr.ReqType
|
||||
case DIRECTION:
|
||||
return storedCdr.Direction
|
||||
case TENANT:
|
||||
return storedCdr.Tenant
|
||||
case Category:
|
||||
return storedCdr.Category
|
||||
case ACCOUNT:
|
||||
return storedCdr.Account
|
||||
case SUBJECT:
|
||||
return storedCdr.Subject
|
||||
case DESTINATION:
|
||||
return storedCdr.Destination
|
||||
case SETUP_TIME:
|
||||
return storedCdr.SetupTime.String()
|
||||
case ANSWER_TIME:
|
||||
return storedCdr.AnswerTime.String()
|
||||
case DURATION:
|
||||
return strconv.FormatFloat(storedCdr.Duration.Seconds(), 'f', -1, 64)
|
||||
case MEDI_RUNID:
|
||||
return storedCdr.MediationRunId
|
||||
case COST:
|
||||
return strconv.FormatFloat(storedCdr.Cost, 'f', -1, 64) // Recommended to use FormatCost
|
||||
default:
|
||||
return storedCdr.ExtraFields[fldName]
|
||||
// Used in mediation, primaryMandatory marks whether missing field out of request represents error or can be ignored
|
||||
func (storedCdr *StoredCdr) ForkCdr(runId string, reqTypeFld, directionFld, tenantFld, categFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld *RSRField,
|
||||
extraFlds []*RSRField, primaryMandatory bool) (*StoredCdr, error) {
|
||||
// MetaDefault will automatically be converted to their standard values
|
||||
if reqTypeFld.Id == META_DEFAULT {
|
||||
reqTypeFld.Id = REQTYPE
|
||||
}
|
||||
}
|
||||
|
||||
// Converts to CgrCdr, so we can fork with less code
|
||||
func (storedCdr *StoredCdr) AsCgrCdr() CgrCdr {
|
||||
cgrCdr := make(CgrCdr)
|
||||
for _, fldName := range PrimaryCdrFields {
|
||||
cgrCdr[fldName] = storedCdr.ExportFieldValue(fldName)
|
||||
if directionFld.Id == META_DEFAULT {
|
||||
directionFld.Id = DIRECTION
|
||||
}
|
||||
return cgrCdr
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) ForkCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld,
|
||||
setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) {
|
||||
return storedCdr.AsCgrCdr().ForkCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld,
|
||||
setupTimeFld, answerTimeFld, durationFld, extraFlds, fieldsMandatory)
|
||||
if tenantFld.Id == META_DEFAULT {
|
||||
tenantFld.Id = TENANT
|
||||
}
|
||||
if categFld.Id == META_DEFAULT {
|
||||
categFld.Id = CATEGORY
|
||||
}
|
||||
if accountFld.Id == META_DEFAULT {
|
||||
accountFld.Id = ACCOUNT
|
||||
}
|
||||
if subjectFld.Id == META_DEFAULT {
|
||||
subjectFld.Id = SUBJECT
|
||||
}
|
||||
if destFld.Id == META_DEFAULT {
|
||||
destFld.Id = DESTINATION
|
||||
}
|
||||
if setupTimeFld.Id == META_DEFAULT {
|
||||
setupTimeFld.Id = SETUP_TIME
|
||||
}
|
||||
if answerTimeFld.Id == META_DEFAULT {
|
||||
answerTimeFld.Id = ANSWER_TIME
|
||||
}
|
||||
if durationFld.Id == META_DEFAULT {
|
||||
durationFld.Id = DURATION
|
||||
}
|
||||
var err error
|
||||
frkStorCdr := new(StoredCdr)
|
||||
frkStorCdr.CgrId = storedCdr.CgrId
|
||||
frkStorCdr.MediationRunId = runId
|
||||
frkStorCdr.Cost = -1.0 // Default for non-rated CDR
|
||||
frkStorCdr.AccId = storedCdr.AccId
|
||||
frkStorCdr.CdrHost = storedCdr.CdrHost
|
||||
frkStorCdr.CdrSource = storedCdr.CdrSource
|
||||
frkStorCdr.ReqType = storedCdr.FieldAsString(reqTypeFld)
|
||||
if primaryMandatory && len(frkStorCdr.ReqType) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, REQTYPE, reqTypeFld.Id))
|
||||
}
|
||||
frkStorCdr.Direction = storedCdr.FieldAsString(directionFld)
|
||||
if primaryMandatory && len(frkStorCdr.Direction) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, DIRECTION, directionFld.Id))
|
||||
}
|
||||
frkStorCdr.Tenant = storedCdr.FieldAsString(tenantFld)
|
||||
if primaryMandatory && len(frkStorCdr.Tenant) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, TENANT, tenantFld.Id))
|
||||
}
|
||||
frkStorCdr.Category = storedCdr.FieldAsString(categFld)
|
||||
if primaryMandatory && len(frkStorCdr.Category) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, CATEGORY, categFld.Id))
|
||||
}
|
||||
frkStorCdr.Account = storedCdr.FieldAsString(accountFld)
|
||||
if primaryMandatory && len(frkStorCdr.Account) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, ACCOUNT, accountFld.Id))
|
||||
}
|
||||
frkStorCdr.Subject = storedCdr.FieldAsString(subjectFld)
|
||||
if primaryMandatory && len(frkStorCdr.Subject) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, SUBJECT, subjectFld.Id))
|
||||
}
|
||||
frkStorCdr.Destination = storedCdr.FieldAsString(destFld)
|
||||
if primaryMandatory && len(frkStorCdr.Destination) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, DESTINATION, destFld.Id))
|
||||
}
|
||||
sTimeStr := storedCdr.FieldAsString(setupTimeFld)
|
||||
if primaryMandatory && len(sTimeStr) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, SETUP_TIME, setupTimeFld.Id))
|
||||
} else if frkStorCdr.SetupTime, err = ParseTimeDetectLayout(sTimeStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aTimeStr := storedCdr.FieldAsString(answerTimeFld)
|
||||
if primaryMandatory && len(aTimeStr) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, ANSWER_TIME, answerTimeFld.Id))
|
||||
} else if frkStorCdr.AnswerTime, err = ParseTimeDetectLayout(aTimeStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
durStr := storedCdr.FieldAsString(durationFld)
|
||||
if primaryMandatory && len(durStr) == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s:%s", ERR_MANDATORY_IE_MISSING, DURATION, durationFld.Id))
|
||||
} else if frkStorCdr.Duration, err = ParseDurationWithSecs(durStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frkStorCdr.ExtraFields = make(map[string]string, len(extraFlds))
|
||||
for _, fld := range extraFlds {
|
||||
frkStorCdr.ExtraFields[fld.Id] = storedCdr.FieldAsString(fld)
|
||||
}
|
||||
return frkStorCdr, nil
|
||||
}
|
||||
|
||||
@@ -25,166 +25,54 @@ import (
|
||||
)
|
||||
|
||||
func TestStoredCdrInterfaces(t *testing.T) {
|
||||
ratedCdr := new(StoredCdr)
|
||||
var _ RawCDR = ratedCdr
|
||||
storedCdr := new(StoredCdr)
|
||||
var _ RawCdr = storedCdr
|
||||
}
|
||||
|
||||
func TestNewStoredCdrFromRawCDR(t *testing.T) {
|
||||
cgrCdr := CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "cdrsource": "internal_test", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call",
|
||||
"account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:20Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10",
|
||||
"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
|
||||
setupTime, _ := ParseTimeDetectLayout(cgrCdr["setup_time"])
|
||||
expctRtCdr := &StoredCdr{CgrId: Sha1(cgrCdr["accid"], setupTime.String()), AccId: cgrCdr["accid"], CdrHost: cgrCdr["cdrhost"], CdrSource: cgrCdr["cdrsource"], ReqType: cgrCdr["reqtype"],
|
||||
Direction: cgrCdr["direction"], Tenant: cgrCdr["tenant"], Category: cgrCdr["tor"], Account: cgrCdr["account"], Subject: cgrCdr["subject"],
|
||||
Destination: cgrCdr["destination"], SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), Duration: time.Duration(10) * time.Second,
|
||||
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, MediationRunId: DEFAULT_RUNID, Cost: -1}
|
||||
if rt, err := NewStoredCdrFromRawCDR(cgrCdr); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rt, expctRtCdr) {
|
||||
t.Errorf("Received %v, expected: %v", rt, expctRtCdr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoredCdrFields(t *testing.T) {
|
||||
ratedCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Unix(1383813746, 0).String()), AccId: "dsafdsaf", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813746, 0), AnswerTime: time.Unix(1383813746, 0), Duration: 10,
|
||||
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
|
||||
}
|
||||
if ratedCdr.GetCgrId() != Sha1("dsafdsaf", time.Unix(1383813746, 0).String()) {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetAccId() != "dsafdsaf" {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetCdrHost() != "192.168.1.1" {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetDirection() != "*out" {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetSubject() != "1001" {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetAccount() != "1001" {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetDestination() != "1002" {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetCategory() != "call" {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetTenant() != "cgrates.org" {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
if ratedCdr.GetReqType() != RATED {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
setupTime, _ := ratedCdr.GetSetupTime()
|
||||
expectedSTime, _ := time.Parse(time.RFC3339, "2013-11-07T08:42:26Z")
|
||||
if setupTime.UTC() != expectedSTime {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
answerTime, _ := ratedCdr.GetAnswerTime()
|
||||
expectedATime, _ := time.Parse(time.RFC3339, "2013-11-07T08:42:26Z")
|
||||
if answerTime.UTC() != expectedATime {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
dur, _ := ratedCdr.GetDuration()
|
||||
if dur != 10 {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
extraFields := ratedCdr.GetExtraFields()
|
||||
if len(extraFields) != 2 {
|
||||
t.Error("Error parsing extra fields: ", extraFields)
|
||||
}
|
||||
if extraFields["field_extr1"] != "val_extr1" {
|
||||
t.Error("Error parsing extra fields: ", extraFields)
|
||||
}
|
||||
if ratedCdr.Cost != 1.01 {
|
||||
t.Error("Error parsing cdr: ", ratedCdr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsRawCdrHttpForm(t *testing.T) {
|
||||
ratedCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
|
||||
Duration: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
|
||||
}
|
||||
cdrForm := ratedCdr.AsRawCdrHttpForm()
|
||||
if cdrForm.Get(ACCID) != ratedCdr.AccId {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.AccId, cdrForm.Get(ACCID))
|
||||
}
|
||||
if cdrForm.Get(CDRHOST) != ratedCdr.CdrHost {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.CdrHost, cdrForm.Get(CDRHOST))
|
||||
}
|
||||
if cdrForm.Get(CDRSOURCE) != ratedCdr.CdrSource {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.CdrSource, cdrForm.Get(CDRSOURCE))
|
||||
}
|
||||
if cdrForm.Get(REQTYPE) != ratedCdr.ReqType {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.ReqType, cdrForm.Get(REQTYPE))
|
||||
}
|
||||
if cdrForm.Get(DIRECTION) != ratedCdr.Direction {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.Direction, cdrForm.Get(DIRECTION))
|
||||
}
|
||||
if cdrForm.Get(TENANT) != ratedCdr.Tenant {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.Tenant, cdrForm.Get(TENANT))
|
||||
}
|
||||
if cdrForm.Get(Category) != ratedCdr.Category {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.Category, cdrForm.Get(Category))
|
||||
}
|
||||
if cdrForm.Get(ACCOUNT) != ratedCdr.Account {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.Account, cdrForm.Get(ACCOUNT))
|
||||
}
|
||||
if cdrForm.Get(SUBJECT) != ratedCdr.Subject {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.Subject, cdrForm.Get(SUBJECT))
|
||||
}
|
||||
if cdrForm.Get(DESTINATION) != ratedCdr.Destination {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.Destination, cdrForm.Get(DESTINATION))
|
||||
}
|
||||
if cdrForm.Get(SETUP_TIME) != "2013-11-07 08:42:20 +0000 UTC" {
|
||||
t.Errorf("Expected: %s, received: %s", "2013-11-07 08:42:26 +0000 UTC", cdrForm.Get(SETUP_TIME))
|
||||
}
|
||||
if cdrForm.Get(ANSWER_TIME) != "2013-11-07 08:42:26 +0000 UTC" {
|
||||
t.Errorf("Expected: %s, received: %s", "2013-11-07 08:42:26 +0000 UTC", cdrForm.Get(ANSWER_TIME))
|
||||
}
|
||||
if cdrForm.Get(DURATION) != "10" {
|
||||
t.Errorf("Expected: %s, received: %s", "10", cdrForm.Get(DURATION))
|
||||
}
|
||||
if cdrForm.Get("field_extr1") != ratedCdr.ExtraFields["field_extr1"] {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.ExtraFields["field_extr1"], cdrForm.Get("field_extr1"))
|
||||
}
|
||||
if cdrForm.Get("fieldextr2") != ratedCdr.ExtraFields["fieldextr2"] {
|
||||
t.Errorf("Expected: %s, received: %s", ratedCdr.ExtraFields["fieldextr2"], cdrForm.Get("fieldextr2"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportFieldValue(t *testing.T) {
|
||||
func TestFieldAsString(t *testing.T) {
|
||||
cdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
|
||||
Duration: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
|
||||
}
|
||||
if cdr.ExportFieldValue(CGRID) != cdr.CgrId ||
|
||||
cdr.ExportFieldValue(ORDERID) != "123" ||
|
||||
cdr.ExportFieldValue(ACCID) != cdr.AccId ||
|
||||
cdr.ExportFieldValue(CDRHOST) != cdr.CdrHost ||
|
||||
cdr.ExportFieldValue(CDRSOURCE) != cdr.CdrSource ||
|
||||
cdr.ExportFieldValue(REQTYPE) != cdr.ReqType ||
|
||||
cdr.ExportFieldValue(DIRECTION) != cdr.Direction ||
|
||||
cdr.ExportFieldValue(TENANT) != cdr.Tenant ||
|
||||
cdr.ExportFieldValue(Category) != cdr.Category ||
|
||||
cdr.ExportFieldValue(ACCOUNT) != cdr.Account ||
|
||||
cdr.ExportFieldValue(SUBJECT) != cdr.Subject ||
|
||||
cdr.ExportFieldValue(DESTINATION) != cdr.Destination ||
|
||||
cdr.ExportFieldValue(SETUP_TIME) != cdr.SetupTime.String() ||
|
||||
cdr.ExportFieldValue(ANSWER_TIME) != cdr.AnswerTime.String() ||
|
||||
cdr.ExportFieldValue(DURATION) != "10" ||
|
||||
cdr.ExportFieldValue(MEDI_RUNID) != cdr.MediationRunId ||
|
||||
cdr.ExportFieldValue(COST) != "1.01" ||
|
||||
cdr.ExportFieldValue("field_extr1") != cdr.ExtraFields["field_extr1"] ||
|
||||
cdr.ExportFieldValue("fieldextr2") != cdr.ExtraFields["fieldextr2"] ||
|
||||
cdr.ExportFieldValue("dummy_field") != "" {
|
||||
t.Error("Unexpected filed value received")
|
||||
if cdr.FieldAsString(&RSRField{Id: CGRID}) != cdr.CgrId ||
|
||||
cdr.FieldAsString(&RSRField{Id: ORDERID}) != "123" ||
|
||||
cdr.FieldAsString(&RSRField{Id: ACCID}) != cdr.AccId ||
|
||||
cdr.FieldAsString(&RSRField{Id: CDRHOST}) != cdr.CdrHost ||
|
||||
cdr.FieldAsString(&RSRField{Id: CDRSOURCE}) != cdr.CdrSource ||
|
||||
cdr.FieldAsString(&RSRField{Id: REQTYPE}) != cdr.ReqType ||
|
||||
cdr.FieldAsString(&RSRField{Id: DIRECTION}) != cdr.Direction ||
|
||||
cdr.FieldAsString(&RSRField{Id: CATEGORY}) != cdr.Category ||
|
||||
cdr.FieldAsString(&RSRField{Id: ACCOUNT}) != cdr.Account ||
|
||||
cdr.FieldAsString(&RSRField{Id: SUBJECT}) != cdr.Subject ||
|
||||
cdr.FieldAsString(&RSRField{Id: DESTINATION}) != cdr.Destination ||
|
||||
cdr.FieldAsString(&RSRField{Id: SETUP_TIME}) != cdr.SetupTime.String() ||
|
||||
cdr.FieldAsString(&RSRField{Id: ANSWER_TIME}) != cdr.AnswerTime.String() ||
|
||||
cdr.FieldAsString(&RSRField{Id: DURATION}) != "10" ||
|
||||
cdr.FieldAsString(&RSRField{Id: MEDI_RUNID}) != cdr.MediationRunId ||
|
||||
cdr.FieldAsString(&RSRField{Id: COST}) != "1.01" ||
|
||||
cdr.FieldAsString(&RSRField{Id: "field_extr1"}) != cdr.ExtraFields["field_extr1"] ||
|
||||
cdr.FieldAsString(&RSRField{Id: "fieldextr2"}) != cdr.ExtraFields["fieldextr2"] ||
|
||||
cdr.FieldAsString(&RSRField{Id: "dummy_field"}) != "" {
|
||||
t.Error("Unexpected filed value received",
|
||||
cdr.FieldAsString(&RSRField{Id: CGRID}) != cdr.CgrId,
|
||||
cdr.FieldAsString(&RSRField{Id: ORDERID}) != "123",
|
||||
cdr.FieldAsString(&RSRField{Id: ACCID}) != cdr.AccId,
|
||||
cdr.FieldAsString(&RSRField{Id: CDRHOST}) != cdr.CdrHost,
|
||||
cdr.FieldAsString(&RSRField{Id: CDRSOURCE}) != cdr.CdrSource,
|
||||
cdr.FieldAsString(&RSRField{Id: REQTYPE}) != cdr.ReqType,
|
||||
cdr.FieldAsString(&RSRField{Id: DIRECTION}) != cdr.Direction,
|
||||
cdr.FieldAsString(&RSRField{Id: CATEGORY}) != cdr.Category,
|
||||
cdr.FieldAsString(&RSRField{Id: ACCOUNT}) != cdr.Account,
|
||||
cdr.FieldAsString(&RSRField{Id: SUBJECT}) != cdr.Subject,
|
||||
cdr.FieldAsString(&RSRField{Id: DESTINATION}) != cdr.Destination,
|
||||
cdr.FieldAsString(&RSRField{Id: SETUP_TIME}) != cdr.SetupTime.String(),
|
||||
cdr.FieldAsString(&RSRField{Id: ANSWER_TIME}) != cdr.AnswerTime.String(),
|
||||
cdr.FieldAsString(&RSRField{Id: DURATION}) != "10",
|
||||
cdr.FieldAsString(&RSRField{Id: MEDI_RUNID}) != cdr.MediationRunId,
|
||||
cdr.FieldAsString(&RSRField{Id: COST}) != "1.01",
|
||||
cdr.FieldAsString(&RSRField{Id: "field_extr1"}) != cdr.ExtraFields["field_extr1"],
|
||||
cdr.FieldAsString(&RSRField{Id: "fieldextr2"}) != cdr.ExtraFields["fieldextr2"],
|
||||
cdr.FieldAsString(&RSRField{Id: "dummy_field"}) != "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,3 +95,138 @@ func TestFormatCost(t *testing.T) {
|
||||
t.Error("Unexpected format of the cost: ", cdr.FormatCost(2, 3))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoredCdrAsHttpForm(t *testing.T) {
|
||||
storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
|
||||
Duration: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
|
||||
}
|
||||
cdrForm := storCdr.AsHttpForm()
|
||||
if cdrForm.Get(ACCID) != "dsafdsaf" {
|
||||
t.Errorf("Expected: %s, received: %s", "dsafdsaf", cdrForm.Get(ACCID))
|
||||
}
|
||||
if cdrForm.Get(CDRHOST) != "192.168.1.1" {
|
||||
t.Errorf("Expected: %s, received: %s", "192.168.1.1", cdrForm.Get(CDRHOST))
|
||||
}
|
||||
if cdrForm.Get(CDRSOURCE) != UNIT_TEST {
|
||||
t.Errorf("Expected: %s, received: %s", UNIT_TEST, cdrForm.Get(CDRSOURCE))
|
||||
}
|
||||
if cdrForm.Get(REQTYPE) != "rated" {
|
||||
t.Errorf("Expected: %s, received: %s", "rated", cdrForm.Get(REQTYPE))
|
||||
}
|
||||
if cdrForm.Get(DIRECTION) != "*out" {
|
||||
t.Errorf("Expected: %s, received: %s", "*out", cdrForm.Get(DIRECTION))
|
||||
}
|
||||
if cdrForm.Get(TENANT) != "cgrates.org" {
|
||||
t.Errorf("Expected: %s, received: %s", "cgrates.org", cdrForm.Get(TENANT))
|
||||
}
|
||||
if cdrForm.Get(CATEGORY) != "call" {
|
||||
t.Errorf("Expected: %s, received: %s", "call", cdrForm.Get(CATEGORY))
|
||||
}
|
||||
if cdrForm.Get(ACCOUNT) != "1001" {
|
||||
t.Errorf("Expected: %s, received: %s", "1001", cdrForm.Get(ACCOUNT))
|
||||
}
|
||||
if cdrForm.Get(SUBJECT) != "1001" {
|
||||
t.Errorf("Expected: %s, received: %s", "1001", cdrForm.Get(SUBJECT))
|
||||
}
|
||||
if cdrForm.Get(DESTINATION) != "1002" {
|
||||
t.Errorf("Expected: %s, received: %s", "1002", cdrForm.Get(DESTINATION))
|
||||
}
|
||||
if cdrForm.Get(SETUP_TIME) != "2013-11-07 08:42:20 +0000 UTC" {
|
||||
t.Errorf("Expected: %s, received: %s", "2013-11-07 08:42:20 +0000 UTC", cdrForm.Get(SETUP_TIME))
|
||||
}
|
||||
if cdrForm.Get(ANSWER_TIME) != "2013-11-07 08:42:26 +0000 UTC" {
|
||||
t.Errorf("Expected: %s, received: %s", "2013-11-07 08:42:26 +0000 UTC", cdrForm.Get(ANSWER_TIME))
|
||||
}
|
||||
if cdrForm.Get(DURATION) != "10" {
|
||||
t.Errorf("Expected: %s, received: %s", "10", cdrForm.Get(DURATION))
|
||||
}
|
||||
if cdrForm.Get("field_extr1") != "val_extr1" {
|
||||
t.Errorf("Expected: %s, received: %s", "val_extr1", cdrForm.Get("field_extr1"))
|
||||
}
|
||||
if cdrForm.Get("fieldextr2") != "valextr2" {
|
||||
t.Errorf("Expected: %s, received: %s", "valextr2", cdrForm.Get("fieldextr2"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoredCdrForkCdr(t *testing.T) {
|
||||
storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
|
||||
Duration: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "field_extr2": "valextr2"}, Cost: 1.01,
|
||||
}
|
||||
rtSampleCdrOut, err := storCdr.ForkCdr("sample_run1", &RSRField{Id: REQTYPE}, &RSRField{Id: DIRECTION}, &RSRField{Id: TENANT}, &RSRField{Id: CATEGORY},
|
||||
&RSRField{Id: ACCOUNT}, &RSRField{Id: SUBJECT}, &RSRField{Id: DESTINATION}, &RSRField{Id: SETUP_TIME}, &RSRField{Id: ANSWER_TIME}, &RSRField{Id: DURATION},
|
||||
[]*RSRField{&RSRField{Id: "field_extr1"}, &RSRField{Id: "field_extr2"}}, true)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error received", err)
|
||||
}
|
||||
expctSplRatedCdr := &StoredCdr{CgrId: storCdr.CgrId, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: UNIT_TEST, ReqType: "rated",
|
||||
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
|
||||
Duration: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "field_extr2": "valextr2"}, MediationRunId: "sample_run1", Cost: -1}
|
||||
if !reflect.DeepEqual(expctSplRatedCdr, rtSampleCdrOut) {
|
||||
t.Errorf("Expected: %v, received: %v", expctSplRatedCdr, rtSampleCdrOut)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoredCdrForkCdrStaticVals(t *testing.T) {
|
||||
storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
|
||||
Duration: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
|
||||
}
|
||||
rsrStPostpaid, _ := NewRSRField("^postpaid")
|
||||
rsrStIn, _ := NewRSRField("^*in")
|
||||
rsrStCgr, _ := NewRSRField("^cgrates.com")
|
||||
rsrStPC, _ := NewRSRField("^premium_call")
|
||||
rsrStFA, _ := NewRSRField("^first_account")
|
||||
rsrStFS, _ := NewRSRField("^first_subject")
|
||||
rsrStST, _ := NewRSRField("^2013-12-07T08:42:24Z")
|
||||
rsrStAT, _ := NewRSRField("^2013-12-07T08:42:26Z")
|
||||
rsrStDur, _ := NewRSRField("^12s")
|
||||
rtCdrOut2, err := storCdr.ForkCdr("wholesale_run", rsrStPostpaid, rsrStIn, rsrStCgr, rsrStPC, rsrStFA, rsrStFS, &RSRField{Id: "destination"}, rsrStST, rsrStAT, rsrStDur,
|
||||
[]*RSRField{}, true)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Unexpected error received", err)
|
||||
}
|
||||
expctRatedCdr2 := &StoredCdr{CgrId: storCdr.CgrId, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: UNIT_TEST, ReqType: "postpaid",
|
||||
Direction: "*in", Tenant: "cgrates.com", Category: "premium_call", Account: "first_account", Subject: "first_subject", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC),
|
||||
AnswerTime: time.Date(2013, 12, 7, 8, 42, 26, 0, time.UTC), Duration: time.Duration(12) * time.Second,
|
||||
ExtraFields: map[string]string{}, MediationRunId: "wholesale_run", Cost: -1}
|
||||
if !reflect.DeepEqual(rtCdrOut2, expctRatedCdr2) {
|
||||
t.Errorf("Received: %v, expected: %v", rtCdrOut2, expctRatedCdr2)
|
||||
}
|
||||
_, err = storCdr.ForkCdr("wholesale_run", &RSRField{Id: "dummy_header"}, &RSRField{Id: "direction"}, &RSRField{Id: "tenant"}, &RSRField{Id: "tor"}, &RSRField{Id: "account"},
|
||||
&RSRField{Id: "subject"}, &RSRField{Id: "destination"}, &RSRField{Id: "setup_time"}, &RSRField{Id: "answer_time"}, &RSRField{Id: "duration"},
|
||||
[]*RSRField{}, true)
|
||||
if err == nil {
|
||||
t.Error("Failed to detect missing header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoredCdrForkCdrFromMetaDefaults(t *testing.T) {
|
||||
storCdr := StoredCdr{CgrId: Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
CdrSource: UNIT_TEST, ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID,
|
||||
Duration: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
|
||||
}
|
||||
expctCdr := &StoredCdr{CgrId: storCdr.CgrId, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: UNIT_TEST, ReqType: "rated",
|
||||
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
|
||||
Duration: time.Duration(10) * time.Second,
|
||||
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, MediationRunId: "wholesale_run", Cost: -1}
|
||||
cdrOut, err := storCdr.ForkCdr("wholesale_run", &RSRField{Id: META_DEFAULT}, &RSRField{Id: META_DEFAULT}, &RSRField{Id: META_DEFAULT}, &RSRField{Id: META_DEFAULT},
|
||||
&RSRField{Id: META_DEFAULT}, &RSRField{Id: META_DEFAULT}, &RSRField{Id: META_DEFAULT},
|
||||
&RSRField{Id: META_DEFAULT}, &RSRField{Id: META_DEFAULT}, &RSRField{Id: META_DEFAULT}, []*RSRField{&RSRField{Id: "field_extr1"}, &RSRField{Id: "fieldextr2"}}, true)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error received", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expctCdr, cdrOut) {
|
||||
t.Errorf("Expected: %v, received: %v", expctCdr, cdrOut)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user