CDRC cdr_type-> cdr_format, csv_separator->field_separator for better generics

This commit is contained in:
DanB
2014-10-05 17:26:15 +02:00
parent 47bc77d8e3
commit b087baf068
16 changed files with 228 additions and 114 deletions

View File

@@ -48,7 +48,7 @@ func NewCdrc(cdrcCfg *config.CdrcConfig, httpSkipTlsCheck bool, cdrServer *engin
return nil, fmt.Errorf("Unsupported csv separator: %s", cdrcCfg.FieldSeparator)
}
csvSepRune, _ := utf8.DecodeRune([]byte(cdrcCfg.FieldSeparator))
cdrc := &Cdrc{cdrsAddress: cdrcCfg.CdrsAddress, cdrType: cdrcCfg.CdrType, cdrInDir: cdrcCfg.CdrInDir, cdrOutDir: cdrcCfg.CdrOutDir,
cdrc := &Cdrc{cdrsAddress: cdrcCfg.CdrsAddress, CdrFormat: cdrcCfg.CdrFormat, cdrInDir: cdrcCfg.CdrInDir, cdrOutDir: cdrcCfg.CdrOutDir,
cdrSourceId: cdrcCfg.CdrSourceId, runDelay: cdrcCfg.RunDelay, csvSep: csvSepRune, cdrFields: cdrcCfg.CdrFields, httpSkipTlsCheck: httpSkipTlsCheck, cdrServer: cdrServer}
// Before processing, make sure in and out folders exist
for _, dir := range []string{cdrc.cdrInDir, cdrc.cdrOutDir} {
@@ -62,7 +62,7 @@ func NewCdrc(cdrcCfg *config.CdrcConfig, httpSkipTlsCheck bool, cdrServer *engin
type Cdrc struct {
cdrsAddress,
cdrType,
CdrFormat,
cdrInDir,
cdrOutDir,
cdrSourceId string
@@ -92,7 +92,7 @@ func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) {
var err error
for _, cdrFldCfg := range self.cdrFields {
var fieldVal string
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cdrType) {
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.CdrFormat) {
if cdrFldCfg.Type == utils.CDRFIELD {
for _, cfgFieldRSR := range cdrFldCfg.Value {
if cfgFieldRSR.IsStatic() {
@@ -122,7 +122,7 @@ func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) {
return nil, fmt.Errorf("Unsupported field type: %s", cdrFldCfg.Type)
}
} else { // Modify here when we add more supported cdr formats
return nil, fmt.Errorf("Unsupported CDR file format: %s", self.cdrType)
return nil, fmt.Errorf("Unsupported CDR file format: %s", self.CdrFormat)
}
switch cdrFldCfg.CdrFieldId {
case utils.TOR:
@@ -169,7 +169,7 @@ func (self *Cdrc) processCdrDir() error {
engine.Logger.Info(fmt.Sprintf("<Cdrc> Parsing folder %s for CDR files.", self.cdrInDir))
filesInDir, _ := ioutil.ReadDir(self.cdrInDir)
for _, file := range filesInDir {
if self.cdrType != FS_CSV || path.Ext(file.Name()) != ".csv" {
if self.CdrFormat != FS_CSV || path.Ext(file.Name()) != ".csv" {
go func() { //Enable async processing here
if err := self.processFile(path.Join(self.cdrInDir, file.Name())); err != nil {
engine.Logger.Err(fmt.Sprintf("Processing file %s, error: %s", file, err.Error()))
@@ -195,7 +195,7 @@ func (self *Cdrc) trackCDRFiles() (err error) {
for {
select {
case ev := <-watcher.Event:
if ev.IsCreate() && (self.cdrType != FS_CSV || path.Ext(ev.Name) != ".csv") {
if ev.IsCreate() && (self.CdrFormat != FS_CSV || path.Ext(ev.Name) != ".csv") {
go func() { //Enable async processing here
if err = self.processFile(ev.Name); err != nil {
engine.Logger.Err(fmt.Sprintf("Processing file %s, error: %s", ev.Name, err.Error()))

View File

@@ -30,7 +30,7 @@ func TestRecordForkCdr(t *testing.T) {
cgrConfig, _ := config.NewDefaultCGRConfig()
cdrcConfig := cgrConfig.CdrcInstances[0]
cdrcConfig.CdrFields = append(cdrcConfig.CdrFields, &config.CfgCdrField{Tag: "SupplierTest", Type: utils.CDRFIELD, CdrFieldId: "supplier", Value: []*utils.RSRField{&utils.RSRField{Id: "14"}}})
cdrc := &Cdrc{cdrType: CSV, cdrSourceId: "TEST_CDRC", cdrFields: cdrcConfig.CdrFields}
cdrc := &Cdrc{CdrFormat: CSV, cdrSourceId: "TEST_CDRC", cdrFields: cdrcConfig.CdrFields}
cdrRow := []string{"firstField", "secondField"}
_, err := cdrc.recordToStoredCdr(cdrRow)
if err == nil {
@@ -152,7 +152,7 @@ func TestDnTdmCdrs(t *testing.T) {
utils.ANSWER_TIME: &utils.RSRField{Id: "4"},
utils.USAGE: usageFld,
}
cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrType, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, ',',
cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrFormat, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, ',',
cgrConfig.CdrcCdrFields, new(cdrs.CDRS), nil}
cdrsContent := bytes.NewReader([]byte(tdmCdrs))
csvReader := csv.NewReader(cdrsContent)

View File

@@ -35,8 +35,8 @@ func NewCdrcConfigFromCgrXmlCdrcCfg(id string, xmlCdrcCfg *CgrXmlCdrcCfg) (*Cdrc
if xmlCdrcCfg.CdrsAddress != nil {
cdrcCfg.CdrsAddress = *xmlCdrcCfg.CdrsAddress
}
if xmlCdrcCfg.CdrType != nil {
cdrcCfg.CdrType = *xmlCdrcCfg.CdrType
if xmlCdrcCfg.CdrFormat != nil {
cdrcCfg.CdrFormat = *xmlCdrcCfg.CdrFormat
}
if xmlCdrcCfg.FieldSeparator != nil {
cdrcCfg.FieldSeparator = *xmlCdrcCfg.FieldSeparator
@@ -47,6 +47,9 @@ func NewCdrcConfigFromCgrXmlCdrcCfg(id string, xmlCdrcCfg *CgrXmlCdrcCfg) (*Cdrc
if xmlCdrcCfg.CdrInDir != nil {
cdrcCfg.CdrInDir = *xmlCdrcCfg.CdrInDir
}
if xmlCdrcCfg.CdrOutDir != nil {
cdrcCfg.CdrOutDir = *xmlCdrcCfg.CdrOutDir
}
if xmlCdrcCfg.CdrSourceId != nil {
cdrcCfg.CdrSourceId = *xmlCdrcCfg.CdrSourceId
}
@@ -54,7 +57,7 @@ func NewCdrcConfigFromCgrXmlCdrcCfg(id string, xmlCdrcCfg *CgrXmlCdrcCfg) (*Cdrc
cdrcCfg.CdrFields = nil // Reinit the fields, so we do not inherit from defaults here
}
for _, xmlCdrField := range xmlCdrcCfg.CdrFields {
if cdrFld, err := NewCfgCdrFieldFromCgrXmlCfgCdrField(xmlCdrField, cdrcCfg.CdrType == utils.CDRE_FIXED_WIDTH); err != nil {
if cdrFld, err := NewCfgCdrFieldFromCgrXmlCfgCdrField(xmlCdrField, cdrcCfg.CdrFormat == utils.CDRE_FIXED_WIDTH); err != nil {
return nil, err
} else {
cdrcCfg.CdrFields = append(cdrcCfg.CdrFields, cdrFld)
@@ -82,7 +85,7 @@ func NewDefaultCdrcConfig() *CdrcConfig {
Id: utils.META_DEFAULT,
Enabled: false,
CdrsAddress: "",
CdrType: utils.CSV,
CdrFormat: utils.CSV,
FieldSeparator: utils.FIELDS_SEP,
RunDelay: time.Duration(0),
CdrInDir: "/var/log/cgrates/cdrc/in",
@@ -102,11 +105,11 @@ func NewCdrcConfigFromFileParams(c *conf.ConfigFile) (*CdrcConfig, error) {
if hasOpt := c.HasOption("cdrc", "cdrs"); hasOpt {
cdrcCfg.CdrsAddress, _ = c.GetString("cdrc", "cdrs")
}
if hasOpt := c.HasOption("cdrc", "cdr_type"); hasOpt {
cdrcCfg.CdrType, _ = c.GetString("cdrc", "cdr_type")
if hasOpt := c.HasOption("cdrc", "cdr_format"); hasOpt {
cdrcCfg.CdrFormat, _ = c.GetString("cdrc", "cdr_format")
}
if hasOpt := c.HasOption("cdrc", "csv_separator"); hasOpt {
cdrcCfg.FieldSeparator, _ = c.GetString("cdrc", "csv_separator")
if hasOpt := c.HasOption("cdrc", "field_separator"); hasOpt {
cdrcCfg.FieldSeparator, _ = c.GetString("cdrc", "field_separator")
}
if hasOpt := c.HasOption("cdrc", "run_delay"); hasOpt {
durStr, _ := c.GetString("cdrc", "run_delay")
@@ -187,7 +190,7 @@ type CdrcConfig struct {
Id string // Configuration label
Enabled bool // Enable/Disable the profile
CdrsAddress string // The address where CDRs can be reached
CdrType string // The type of CDR file to process <csv>
CdrFormat string // The type of CDR file to process <csv>
FieldSeparator string // The separator to use when reading csvs
RunDelay time.Duration // Delay between runs, 0 for inotify driven requests
CdrInDir string // Folder to process CDRs from

View File

@@ -31,7 +31,7 @@ func TestNewDefaultCdrcConfig(t *testing.T) {
Id: utils.META_DEFAULT,
Enabled: false,
CdrsAddress: "",
CdrType: utils.CSV,
CdrFormat: utils.CSV,
FieldSeparator: utils.FIELDS_SEP,
RunDelay: time.Duration(0),
CdrInDir: "/var/log/cgrates/cdrc/in",

View File

@@ -230,7 +230,7 @@ func (self *CGRConfig) checkConfigSanity() error {
if len(cdrcInst.CdrFields) == 0 {
return errors.New("CdrC enabled but no fields to be processed defined!")
}
if cdrcInst.CdrType == utils.CSV {
if cdrcInst.CdrFormat == utils.CSV {
for _, cdrFld := range cdrcInst.CdrFields {
for _, rsrFld := range cdrFld.Value {
if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil {

View File

@@ -232,7 +232,7 @@ func TestConfigFromFile(t *testing.T) {
cdrcCfg.Enabled = true
cdrcCfg.CdrsAddress = "test"
cdrcCfg.RunDelay = time.Duration(99) * time.Second
cdrcCfg.CdrType = "test"
cdrcCfg.CdrFormat = "test"
cdrcCfg.FieldSeparator = ";"
cdrcCfg.CdrInDir = "test"
cdrcCfg.CdrOutDir = "test"

View File

@@ -65,8 +65,8 @@ export_template = test # List of fields in the exported CDRs
enabled = true # Enable CDR client functionality
cdrs = test # Address where to reach CDR server
run_delay = 99 # Period to sleep between two runs, 0 to use automation via inotify
cdr_type = test # CDR file format <csv>.
csv_separator =; # Csv separator, one character only and should be next to equal sign
cdr_format = test # CDR file format <csv>.
field_separator =; # Csv separator, one character only and should be next to equal sign
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.

View File

@@ -50,7 +50,7 @@ func TestSetDefaults(t *testing.T) {
dfCfg, _ := NewDefaultCGRConfig()
xmlCdrc.setDefaults()
if xmlCdrc.CdrsAddress != dfCfg.CdrcCdrs ||
xmlCdrc.CdrType != dfCfg.CdrcCdrType ||
xmlCdrc.CdrFormat != dfCfg.CdrcCdrType ||
xmlCdrc.CsvSeparator != dfCfg.CdrcCsvSep ||
xmlCdrc.CdrInDir != dfCfg.CdrcCdrInDir ||
xmlCdrc.CdrOutDir != dfCfg.CdrcCdrOutDir ||
@@ -67,7 +67,7 @@ func TestParseXmlCdrcConfig(t *testing.T) {
<configuration section="cdrc" id="CDRC-CSV1">
<enabled>true</enabled>
<cdrs_address>internal</cdrs_address>
<cdr_type>csv</cdr_type>
<cdr_format>csv</cdr_format>
<field_separator>,</field_separator>
<run_delay>0</run_delay>
<cdr_in_dir>/var/log/cgrates/cdrc/in</cdr_in_dir>
@@ -109,13 +109,13 @@ func TestGetCdrcCfgs(t *testing.T) {
}
enabled := true
cdrsAddr := "internal"
cdrType := "csv"
cdrFormat := "csv"
fldSep := ","
runDelay := int64(0)
cdrInDir := "/var/log/cgrates/cdrc/in"
cdrOutDir := "/var/log/cgrates/cdrc/out"
cdrSrcId := "freeswitch_csv"
expectCdrc := &CgrXmlCdrcCfg{Enabled: &enabled, CdrsAddress: &cdrsAddr, CdrType: &cdrType, FieldSeparator: &fldSep,
expectCdrc := &CgrXmlCdrcCfg{Enabled: &enabled, CdrsAddress: &cdrsAddr, CdrFormat: &cdrFormat, FieldSeparator: &fldSep,
RunDelay: &runDelay, CdrInDir: &cdrInDir, CdrOutDir: &cdrOutDir, CdrSourceId: &cdrSrcId}
accIdTag, reqTypeTag, dirTag, tntTag, categTag, acntTag, subjTag, dstTag, sTimeTag, aTimeTag, usageTag, extr1, extr2 := utils.ACCID,
utils.REQTYPE, utils.DIRECTION, utils.TENANT, utils.CATEGORY, utils.ACCOUNT, utils.SUBJECT, utils.DESTINATION, utils.SETUP_TIME, utils.ANSWER_TIME, utils.USAGE, "extr1", "extr2"

View File

@@ -57,7 +57,7 @@ type XmlCfgCdrField struct {
type CgrXmlCdrcCfg struct {
Enabled *bool `xml:"enabled"` // Enable/Disable the
CdrsAddress *string `xml:"cdrs_address"` // The address where CDRs can be reached
CdrType *string `xml:"cdr_type"` // The type of CDR to process <csv>
CdrFormat *string `xml:"cdr_format"` // The type of CDR to process <csv>
FieldSeparator *string `xml:"field_separator"` // The separator to use when reading csvs
RunDelay *int64 `xml:"run_delay"` // Delay between runs
CdrInDir *string `xml:"cdr_in_dir"` // Folder to process CDRs from

View File

@@ -69,8 +69,8 @@
# enabled = false # Enable CDR client functionality
# cdrs = internal # Address where to reach CDR server. <internal|127.0.0.1:2080>
# run_delay = 0 # Sleep interval in seconds between consecutive runs, 0 to use automation via inotify
# cdr_type = csv # CDR file format <csv|freeswitch_csv>.
# csv_separator = , # Separator used in case of csv files. One character only supported and needs to be right after equal sign
# cdr_format = csv # CDR file format <csv|freeswitch_csv>.
# field_separator = , # Separator used in case of csv files. One character only supported and needs to be right after equal sign
# cdr_in_dir = /var/log/cgrates/cdrc/in # Absolute path towards the directory where the CDRs are stored.
# cdr_out_dir = /var/log/cgrates/cdrc/out # Absolute path towards the directory where processed CDRs will be moved.
# cdr_source_id = csv # Free form field, tag identifying the source of the CDRs within CGRS database.

View File

@@ -1,51 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdrc" type="csv" id="CDRC-CSV2">
<configuration section="cdrc" id="CDRC-CSV2">
<enabled>true</enabled>
<cdrs_address>internal</cdrs_address>
<cdr_type>csv</cdr_type>
<csv_separator>,</csv_separator>
<field_separator>,</field_separator>
<run_delay>0</run_delay>
<cdr_in_dir>/tmp/cgrates/cdrc2/in</cdr_in_dir>
<cdr_out_dir>/tmp/cgrates/cdrc2/out</cdr_out_dir>
<cdr_source_id>csv2</cdr_source_id>
<fields>
<field id="tor" filter="~7:s/^(voice|data|sms)$/*$1/" />
<field id="accid" filter="0" />
<field id="reqtype" filter="^rated" />
<field id="direction" filter="^*out" />
<field id="tenant" filter="^cgrates.org" />
<field id="category" filter="~7:s/^voice$/call/" />
<field id="account" filter="3" />
<field id="subject" filter="3" />
<field id="destination" filter="~5:s/^0([1-9]\d+)$/+49$1/" />
<field id="setup_time" filter="1" />
<field id="answer_time" filter="1" />
<field id="usage" filter="~9:s/^(\d+)$/${1}s/" />
<field cdr_field="tor" filter="~7:s/^(voice|data|sms)$/*$1/" />
<field cdr_field="accid" filter="0" />
<field cdr_field="reqtype" filter="^rated" />
<field cdr_field="direction" filter="^*out" />
<field cdr_field="tenant" filter="^cgrates.org" />
<field cdr_field="category" filter="~7:s/^voice$/call/" />
<field cdr_field="account" filter="3" />
<field cdr_field="subject" filter="3" />
<field cdr_field="destination" filter="~5:s/^0([1-9]\d+)$/+49$1/" />
<field cdr_field="setup_time" filter="1" />
<field cdr_field="answer_time" filter="1" />
<field cdr_field="usage" filter="~9:s/^(\d+)$/${1}s/" />
</fields>
</configuration>
<configuration section="cdrc" type="csv" id="CDRC-CSV3">
<configuration section="cdrc" id="CDRC-CSV3">
<enabled>true</enabled>
<cdrs_address>internal</cdrs_address>
<cdr_type>csv</cdr_type>
<csv_separator>;</csv_separator>
<field_separator>;</field_separator>
<run_delay>0</run_delay>
<cdr_in_dir>/tmp/cgrates/cdrc3/in</cdr_in_dir>
<cdr_out_dir>/tmp/cgrates/cdrc3/out</cdr_out_dir>
<cdr_source_id>csv3</cdr_source_id>
<fields>
<field id="tor" filter="^*voice" />
<field id="accid" filter="~3:s/^(\d{2})\.(\d{2})\.(\d{4})\s{2}(\d{2}):(\d{2}):(\d{2})$/$1$2$3$4$5$6/" />
<field id="reqtype" filter="^rated" />
<field id="direction" filter="^*out" />
<field id="tenant" filter="^cgrates.org" />
<field id="category" filter="^call" />
<field id="account" filter="~0:s/^([1-9]\d+)$/+$1/" />
<field id="subject" filter="~0:s/^([1-9]\d+)$/+$1/" />
<field id="destination" filter="~1:s/^([1-9]\d+)$/+$1/" />
<field id="setup_time" filter="4" />
<field id="answer_time" filter="4" />
<field id="usage" filter="~6:s/^(\d+)$/${1}s/" />
<field cdr_field="tor" filter="^*voice" />
<field cdr_field="accid" filter="~3:s/^(\d{2})\.(\d{2})\.(\d{4})\s{2}(\d{2}):(\d{2}):(\d{2})$/$1$2$3$4$5$6/" />
<field cdr_field="reqtype" filter="^rated" />
<field cdr_field="direction" filter="^*out" />
<field cdr_field="tenant" filter="^cgrates.org" />
<field cdr_field="category" filter="^call" />
<field cdr_field="account" filter="~0:s/^([1-9]\d+)$/+$1/" />
<field cdr_field="subject" filter="~0:s/^([1-9]\d+)$/+$1/" />
<field cdr_field="destination" filter="~1:s/^([1-9]\d+)$/+$1/" />
<field cdr_field="setup_time" filter="4" />
<field cdr_field="answer_time" filter="4" />
<field cdr_field="usage" filter="~6:s/^(\d+)$/${1}s/" />
</fields>
</configuration>
<configuration section="cdre" type="fwv" id="CDRE-FW1">
@@ -59,51 +59,51 @@
<export_template>
<header>
<fields>
<field name="ToR" type="constant" value="10" width="2" />
<field name="Filler1" type="filler" width="3" />
<field name="FileType" type="constant" value="SIP" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field name="LastCdr" type="metatag" value="last_cdr_atime" layout="020106150405" width="12" />
<field name="FileCreationfTime" type="metatag" value="time_now" layout="020106150405" width="12" />
<field name="FileVersion" type="constant" value="01" width="2" />
<field name="Filler2" type="filler" width="105" />
<field tag="ToR" type="constant" value="10" width="2" />
<field tag="Filler1" type="filler" width="3" />
<field tag="FileType" type="constant" value="SIP" width="3" />
<field tag="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field tag="LastCdr" type="metatag" value="last_cdr_atime" layout="020106150405" width="12" />
<field tag="FileCreationfTime" type="metatag" value="time_now" layout="020106150405" width="12" />
<field tag="FileVersion" type="constant" value="01" width="2" />
<field tag="Filler2" type="filler" width="105" />
</fields>
</header>
<content>
<fields>
<field name="ToR" type="constant" value="20" width="2" />
<field name="Subject" type="cdrfield" value="subject" width="12" padding="right" mandatory="true" />
<field name="ConnectionNumber" type="constant" value="00000" width="5" />
<field name="CallerId" type="cdrfield" value="~callerid:s/\+(\d+)/00$1/" strip="xright" width="15" padding="right" />
<field name="Destination" type="cdrfield" value="~destination:s/^\+311400(\d+)/$1/:s/^\+311412\d\d112/112/:s/^\+31(\d+)/0$1/:s/^\+(\d+)/00$1/" strip="xright" width="24" padding="right" mandatory="true" />
<field name="TypeOfService" type="constant" value="00" width="2" />
<field name="ServiceId" type="constant" value="11" width="4" padding="right" />
<field name="AnswerTime" type="cdrfield" value="answer_time" layout="020106150405" width="12" mandatory="true" />
<field name="Usage" type="cdrfield" value="usage" layout="seconds" width="6" padding="right" mandatory="true" />
<field name="DataCounter" type="filler" width="6" />
<field name="VatCode" type="constant" value="1" width="1" />
<field name="NetworkId" type="constant" value="S1" width="2" />
<field name="DestinationSubId" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\w{5})&quot;/$1/:s/(\w{6})/$1/" width="5" />
<field name="NetworkSubtype" type="constant" value="3" width="1" padding="left" />
<field name="CgrId" type="cdrfield" value="cgrid" strip="xleft" width="16" paddingi="right" mandatory="true" />
<field name="FillerVolume1" type="filler" width="8" />
<field name="FillerVolume2" type="filler" width="8" />
<field name="DestinationSubId" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\w{5})&quot;/$1/:s/(\w{6})/$1/" width="5" />
<field name="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9" />
<field name="MaskDestination" type="metatag" value="mask_destination" width="1" />
<field tag="ToR" type="constant" value="20" width="2" />
<field tag="Subject" type="cdrfield" value="subject" width="12" padding="right" mandatory="true" />
<field tag="ConnectionNumber" type="constant" value="00000" width="5" />
<field tag="CallerId" type="cdrfield" value="~callerid:s/\+(\d+)/00$1/" strip="xright" width="15" padding="right" />
<field tag="Destination" type="cdrfield" value="~destination:s/^\+311400(\d+)/$1/:s/^\+311412\d\d112/112/:s/^\+31(\d+)/0$1/:s/^\+(\d+)/00$1/" strip="xright" width="24" padding="right" mandatory="true" />
<field tag="TypeOfService" type="constant" value="00" width="2" />
<field tag="ServiceId" type="constant" value="11" width="4" padding="right" />
<field tag="AnswerTime" type="cdrfield" value="answer_time" layout="020106150405" width="12" mandatory="true" />
<field tag="Usage" type="cdrfield" value="usage" layout="seconds" width="6" padding="right" mandatory="true" />
<field tag="DataCounter" type="filler" width="6" />
<field tag="VatCode" type="constant" value="1" width="1" />
<field tag="NetworkId" type="constant" value="S1" width="2" />
<field tag="DestinationSubId" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\w{5})&quot;/$1/:s/(\w{6})/$1/" width="5" />
<field tag="NetworkSubtype" type="constant" value="3" width="1" padding="left" />
<field tag="CgrId" type="cdrfield" value="cgrid" strip="xleft" width="16" paddingi="right" mandatory="true" />
<field tag="FillerVolume1" type="filler" width="8" />
<field tag="FillerVolume2" type="filler" width="8" />
<field tag="DestinationSubId" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\w{5})&quot;/$1/:s/(\w{6})/$1/" width="5" />
<field tag="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9" />
<field tag="MaskDestination" type="metatag" value="mask_destination" width="1" />
</fields>
</content>
<trailer>
<fields>
<field name="ToR" type="constant" value="90" width="2" />
<field name="Filler1" type="filler" width="3" />
<field name="FileType" type="constant" value="SIP" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field name="TotalRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6" />
<field name="TotalDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8" />
<field name="FirstCdrTime" type="metatag" value="first_cdr_atime" layout="020106150405" width="12" />
<field name="LastCdrTime" type="metatag" value="last_cdr_atime" layout="020106150405" width="12" />
<field name="Filler1" type="filler" width="93" />
<field tag="ToR" type="constant" value="90" width="2" />
<field tag="Filler1" type="filler" width="3" />
<field tag="FileType" type="constant" value="SIP" width="3" />
<field tag="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field tag="TotalRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6" />
<field tag="TotalDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8" />
<field tag="FirstCdrTime" type="metatag" value="first_cdr_atime" layout="020106150405" width="12" />
<field tag="LastCdrTime" type="metatag" value="last_cdr_atime" layout="020106150405" width="12" />
<field tag="Filler1" type="filler" width="93" />
</fields>
</trailer>
</export_template>

View File

@@ -261,10 +261,11 @@ func (at *ActionTiming) Execute() (err error) {
Logger.Warning(fmt.Sprintf("Could not get user balances for this id: %s. Skipping!", ubId))
return 0, err
} else if ub.Disabled && a.ActionType != ENABLE_ACCOUNT {
return 0, fmt.Errorf("User %s is disabled", ubId)
return 0, fmt.Errorf("Account %s is disabled", ubId)
}
//Logger.Info(fmt.Sprintf("Executing %v on %v", a.ActionType, ub.Id))
//Logger.Info(fmt.Sprintf("Executing %v on %+v", a.ActionType, ub))
err = actionFunction(ub, nil, a)
//Logger.Info(fmt.Sprintf("After execute, account: %+v", ub))
accountingStorage.SetAccount(ub)
return 0, nil
})

View File

@@ -379,15 +379,15 @@ func (rs *RedisStorage) RemoveRpAliases(tenantRtSubjects []*TenantRatingSubject)
return err
}
for _, key := range alsKeys {
alias, err := rs.GetRpAlias(key[len(RP_ALIAS_PREFIX):], true)
if err != nil {
return err
}
for _, tntRSubj := range tenantRtSubjects {
tenantPrfx := RP_ALIAS_PREFIX + tntRSubj.Tenant + utils.CONCATENATED_KEY_SEP
if len(key) < len(tenantPrfx) || tenantPrfx != key[:len(tenantPrfx)] { // filter out the tenant for accounts
continue
}
alias, err := rs.GetRpAlias(key[len(RP_ALIAS_PREFIX):], true)
if err != nil {
return err
}
if tntRSubj.Subject != alias {
continue
}
@@ -477,15 +477,15 @@ func (rs *RedisStorage) RemoveAccAliases(tenantAccounts []*TenantAccount) (err e
return err
}
for _, key := range alsKeys {
alias, err := rs.GetAccAlias(key[len(ACC_ALIAS_PREFIX):], true)
if err != nil {
return err
}
for _, tntAcnt := range tenantAccounts {
tenantPrfx := ACC_ALIAS_PREFIX + tntAcnt.Tenant + utils.CONCATENATED_KEY_SEP
if len(key) < len(tenantPrfx) || tenantPrfx != key[:len(tenantPrfx)] { // filter out the tenant for accounts
continue
}
alias, err := rs.GetAccAlias(key[len(ACC_ALIAS_PREFIX):], true)
if err != nil {
return err
}
if tntAcnt.Account != alias {
continue
}

View File

@@ -0,0 +1,108 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package general_tests
import (
"github.com/cgrates/cgrates/engine"
"reflect"
"testing"
)
var ratingDbAcntActs engine.RatingStorage
var acntDbAcntActs engine.AccountingStorage
func TestAcntActsSetStorage(t *testing.T) {
ratingDbAcntActs, _ = engine.NewMapStorageJson()
engine.SetRatingStorage(ratingDbAcntActs)
acntDbAcntActs, _ = engine.NewMapStorageJson()
engine.SetAccountingStorage(acntDbAcntActs)
}
func TestAcntActsLoadCsv(t *testing.T) {
timings := `ASAP,*any,*any,*any,*any,*asap`
destinations := ``
rates := ``
destinationRates := ``
ratingPlans := ``
ratingProfiles := ``
sharedGroups := ``
lcrs := ``
actions := `TOPUP10_AC,*topup_reset,,*voice,*out,,*any,,,*unlimited,10,10,10
DISABLE_ACNT,*disable_account,,,,,,,,,,,10
ENABLE_ACNT,*enable_account,,,,,,,,,,,10`
actionPlans := `TOPUP10_AT,TOPUP10_AC,ASAP,10`
actionTriggers := ``
accountActions := `cgrates.org,1,*out,TOPUP10_AT,`
derivedCharges := ``
cdrStats := ``
csvr := engine.NewStringCSVReader(ratingDbAcntActs, acntDbAcntActs, ',', destinations, timings, rates, destinationRates, ratingPlans, ratingProfiles,
sharedGroups, lcrs, actions, actionPlans, actionTriggers, accountActions, derivedCharges, cdrStats)
if err := csvr.LoadAll(); err != nil {
t.Fatal(err)
}
csvr.WriteToDatabase(false, false)
ratingDbAcntActs.CacheRating(nil, nil, nil, nil, nil)
acntDbAcntActs.CacheAccounting(nil, nil, nil, nil)
expectAcnt := &engine.Account{Id: "*out:cgrates.org:1"}
if acnt, err := acntDbAcntActs.GetAccount("*out:cgrates.org:1"); err != nil {
t.Error(err)
} else if acnt == nil {
t.Error("No account created")
} else if !reflect.DeepEqual(expectAcnt, acnt) {
t.Errorf("Expecting: %+v, received: %+v", expectAcnt, acnt)
}
}
/*
//ToDo
// This test fails due to Disabled which is ignored on SetAccount with partial content. SetAccount should only be called with account when no previous one set in dataDb.
func TestAcntActsDisableAcnt(t *testing.T) {
acnt1Tag := "*out:cgrates.org:1"
at := &engine.ActionTiming{
AccountIds: []string{acnt1Tag},
ActionsId: "DISABLE_ACNT",
}
if err := at.Execute(); err != nil {
t.Error(err)
}
expectAcnt := &engine.Account{Id: "*out:cgrates.org:1", Disabled: true}
if acnt, err := acntDbAcntActs.GetAccount(acnt1Tag); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectAcnt, acnt) {
t.Errorf("Expecting: %+v, received: %+v", expectAcnt, acnt)
}
}
*/
func TestAcntActsEnableAcnt(t *testing.T) {
acnt1Tag := "*out:cgrates.org:1"
at := &engine.ActionTiming{
AccountIds: []string{acnt1Tag},
ActionsId: "ENABLE_ACNT",
}
if err := at.Execute(); err != nil {
t.Error(err)
}
expectAcnt := &engine.Account{Id: "*out:cgrates.org:1", Disabled: false}
if acnt, err := acntDbAcntActs.GetAccount(acnt1Tag); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectAcnt, acnt) {
t.Errorf("Expecting: %+v, received: %+v", expectAcnt, acnt)
}
}

View File

@@ -65,6 +65,7 @@ func startEngine() error {
func stopEngine() error {
exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
return nil
}
@@ -96,14 +97,14 @@ func TestCreateCdrDirs(t *testing.T) {
if !*testLocal {
return
}
for _, cdrcDir := range []string{cfg.CdrcInstances[0].CdrInDir, cfg.CdrcInstances[0].CdrOutDir,
*cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV2")["CDRC-CSV2"].CdrInDir, *cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV2")["CDRC-CSV2"].CdrOutDir,
*cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV3")["CDRC-CSV3"].CdrInDir, *cfg.XmlCfgDocument.GetCdrcCfgs("CDRC-CSV3")["CDRC-CSV3"].CdrOutDir} {
if err := os.RemoveAll(cdrcDir); err != nil {
t.Fatal("Error removing folder: ", cdrcDir, err)
}
if err := os.MkdirAll(cdrcDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cdrcDir, err)
for _, cdrcInst := range cfg.CdrcInstances {
for _, dir := range []string{cdrcInst.CdrInDir, cdrcInst.CdrOutDir} {
if err := os.RemoveAll(dir); err != nil {
t.Fatal("Error removing folder: ", dir, err)
}
if err := os.MkdirAll(dir, 0755); err != nil {
t.Fatal("Error creating folder: ", dir, err)
}
}
}
}
@@ -114,7 +115,6 @@ func TestRpcConn(t *testing.T) {
return
}
startEngine()
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
var err error
rater, err = jsonrpc.Dial("tcp", cfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
if err != nil {

View File

@@ -14,11 +14,13 @@ go test github.com/cgrates/cgrates/config -local
cfg=$?
go test github.com/cgrates/cgrates/utils -local
utl=$?
go test github.com/cgrates/cgrates/general_tests -local
gnr=$?
exit $gen && $ap1 && $ap2 && $en && $cdrc && $cfg && $utl
exit $gen && $ap1 && $ap2 && $en && $cdrc && $cfg && $utl && $gnr