Refactoring the code around derived charging to support integration with responder for internal requests, some of the copyright headers updated with new slogan

This commit is contained in:
DanB
2014-04-23 20:48:43 +02:00
parent bba4a878f0
commit 2a253892c5
27 changed files with 381 additions and 333 deletions

View File

@@ -1,4 +1,4 @@
## Rating system for Telecom & ISP environments ##
## Real-time Charging System for Telecom & ISP environments ##
[![Build Status](https://drone.io/github.com/cgrates/cgrates/status.png)](https://drone.io/github.com/cgrates/cgrates/latest) [![Build Status](https://secure.travis-ci.org/cgrates/cgrates.png)](http://travis-ci.org/cgrates/cgrates)

View File

@@ -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

View File

@@ -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

View File

@@ -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
@@ -119,37 +119,38 @@ type CGRConfig struct {
CdrcExtraFields []string // Extra fields to extract, special format in case of .csv "field1:index1,field2:index2"
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
SMRaterReconnects int // Number of reconnect attempts to rater
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
SMMaxCallDuration time.Duration // The maximum duration of a call
MediatorEnabled bool // Starts Mediator service: <true|false>.
MediatorRater string // Address where to reach the Rater: <internal|x.y.z.y:1234>
MediatorRaterReconnects int // Number of reconnects to rater before giving up.
MediatorRunIds []string // Identifiers for each mediation run on CDRs
MediatorReqTypeFields []string // Name of request type fields to be used during mediation. Use index number in case of .csv cdrs.
MediatorDirectionFields []string // Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorTenantFields []string // Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorTORFields []string // Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorAccountFields []string // Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorSubjectFields []string // Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorDestFields []string // Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorSetupTimeFields []string // Name of setup_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorAnswerTimeFields []string // Name of answer_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorDurationFields []string // Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs.
DerivedChargers DerivedChargers // System wide derived chargers, added to the account level ones
FreeswitchServer string // freeswitch address host:port
FreeswitchPass string // FS socket password
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
HistoryAgentEnabled bool // Starts History as an agent: <true|false>.
HistoryServer string // Address where to reach the master history server: <internal|x.y.z.y:1234>
HistoryServerEnabled bool // Starts History as server: <true|false>.
HistoryDir string // Location on disk where to store history files.
HistorySaveInterval time.Duration // The timout duration between history writes
MailerServer string // The server to use when sending emails out
MailerAuthUser string // Authenticate to email server using this user
MailerAuthPass string // Authenticate to email server with this password
MailerFromAddr string // From address used when sending emails out
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
SMRaterReconnects int // Number of reconnect attempts to rater
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
SMMaxCallDuration time.Duration // The maximum duration of a call
MediatorEnabled bool // Starts Mediator service: <true|false>.
MediatorRater string // Address where to reach the Rater: <internal|x.y.z.y:1234>
MediatorRaterReconnects int // Number of reconnects to rater before giving up.
MediatorRunIds []string // Identifiers for each mediation run on CDRs
MediatorReqTypeFields []string // Name of request type fields to be used during mediation. Use index number in case of .csv cdrs.
MediatorDirectionFields []string // Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorTenantFields []string // Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorTORFields []string // Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorAccountFields []string // Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorSubjectFields []string // Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorDestFields []string // Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorSetupTimeFields []string // Name of setup_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorAnswerTimeFields []string // Name of answer_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorDurationFields []string // Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs.
DerivedChargers utils.DerivedChargers // System wide derived chargers, added to the account level ones
CombinedDerivedChargers bool // Combine accounts specific derived_chargers with server configured
FreeswitchServer string // freeswitch address host:port
FreeswitchPass string // FS socket password
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
HistoryAgentEnabled bool // Starts History as an agent: <true|false>.
HistoryServer string // Address where to reach the master history server: <internal|x.y.z.y:1234>
HistoryServerEnabled bool // Starts History as server: <true|false>.
HistoryDir string // Location on disk where to store history files.
HistorySaveInterval time.Duration // The timout duration between history writes
MailerServer string // The server to use when sending emails out
MailerAuthUser string // Authenticate to email server using this user
MailerAuthPass string // Authenticate to email server with this password
MailerFromAddr string // From address used when sending emails out
}
func (self *CGRConfig) setDefaults() error {
@@ -217,7 +218,8 @@ func (self *CGRConfig) setDefaults() error {
self.MediatorEnabled = false
self.MediatorRater = "internal"
self.MediatorRaterReconnects = 3
self.DerivedChargers = make(DerivedChargers, 0)
self.DerivedChargers = make(utils.DerivedChargers, 0)
self.CombinedDerivedChargers = true
self.SMEnabled = false
self.SMSwitchType = FS
self.SMRater = "internal"
@@ -573,6 +575,9 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if cfg.DerivedChargers, err = ParseCfgDerivedCharging(c); err != nil {
return nil, err
}
if hasOpt = c.HasOption("derived_charging", "combined_chargers"); hasOpt {
cfg.CombinedDerivedChargers, _ = c.GetBool("derived_charging", "combined_chargers")
}
if hasOpt = c.HasOption("history_agent", "enabled"); hasOpt {
cfg.HistoryAgentEnabled, _ = c.GetBool("history_agent", "enabled")
}

View File

@@ -119,7 +119,8 @@ func TestDefaults(t *testing.T) {
eCfg.FreeswitchServer = "127.0.0.1:8021"
eCfg.FreeswitchPass = "ClueCon"
eCfg.FreeswitchReconnects = 5
eCfg.DerivedChargers = make(DerivedChargers, 0)
eCfg.DerivedChargers = make(utils.DerivedChargers, 0)
eCfg.CombinedDerivedChargers = true
eCfg.HistoryAgentEnabled = false
eCfg.HistoryServer = "internal"
eCfg.HistoryServerEnabled = false
@@ -252,8 +253,9 @@ func TestConfigFromFile(t *testing.T) {
eCfg.FreeswitchServer = "test"
eCfg.FreeswitchPass = "test"
eCfg.FreeswitchReconnects = 99
eCfg.DerivedChargers = DerivedChargers{&DerivedCharger{RunId: "test", ReqTypeField: "test", DirectionField: "test", TenantField: "test",
eCfg.DerivedChargers = utils.DerivedChargers{&utils.DerivedCharger{RunId: "test", ReqTypeField: "test", DirectionField: "test", TenantField: "test",
TorField: "test", AccountField: "test", SubjectField: "test", DestinationField: "test", SetupTimeField: "test", AnswerTimeField: "test", DurationField: "test"}}
eCfg.CombinedDerivedChargers = true
eCfg.HistoryAgentEnabled = true
eCfg.HistoryServer = "test"
eCfg.HistoryServerEnabled = true

View File

@@ -1,209 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
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 config
import (
"code.google.com/p/goconf/conf"
"errors"
"github.com/cgrates/cgrates/utils"
"strings"
)
// Wraps regexp compiling in case of rsr fields
func NewDerivedCharger(runId, reqTypeFld, dirFld, tenantFld, totFld, acntFld, subjFld, dstFld, sTimeFld, aTimeFld, durFld string) (dc *DerivedCharger, err error) {
if len(runId) == 0 {
return nil, errors.New("Empty run id field")
}
dc = &DerivedCharger{RunId: runId}
dc.ReqTypeField = reqTypeFld
if strings.HasPrefix(dc.ReqTypeField, utils.REGEXP_PREFIX) {
if dc.rsrReqTypeField, err = utils.NewRSRField(dc.ReqTypeField); err != nil {
return nil, err
}
}
dc.DirectionField = dirFld
if strings.HasPrefix(dc.DirectionField, utils.REGEXP_PREFIX) {
if dc.rsrDirectionField, err = utils.NewRSRField(dc.DirectionField); err != nil {
return nil, err
}
}
dc.TenantField = tenantFld
if strings.HasPrefix(dc.TenantField, utils.REGEXP_PREFIX) {
if dc.rsrTenantField, err = utils.NewRSRField(dc.TenantField); err != nil {
return nil, err
}
}
dc.TorField = totFld
if strings.HasPrefix(dc.TorField, utils.REGEXP_PREFIX) {
if dc.rsrTorField, err = utils.NewRSRField(dc.TorField); err != nil {
return nil, err
}
}
dc.AccountField = acntFld
if strings.HasPrefix(dc.AccountField, utils.REGEXP_PREFIX) {
if dc.rsrAccountField, err = utils.NewRSRField(dc.AccountField); err != nil {
return nil, err
}
}
dc.SubjectField = subjFld
if strings.HasPrefix(dc.SubjectField, utils.REGEXP_PREFIX) {
if dc.rsrSubjectField, err = utils.NewRSRField(dc.SubjectField); err != nil {
return nil, err
}
}
dc.DestinationField = dstFld
if strings.HasPrefix(dc.DestinationField, utils.REGEXP_PREFIX) {
if dc.rsrDestinationField, err = utils.NewRSRField(dc.DestinationField); err != nil {
return nil, err
}
}
dc.SetupTimeField = sTimeFld
if strings.HasPrefix(dc.SetupTimeField, utils.REGEXP_PREFIX) {
if dc.rsrSetupTimeField, err = utils.NewRSRField(dc.SetupTimeField); err != nil {
return nil, err
}
}
dc.AnswerTimeField = aTimeFld
if strings.HasPrefix(dc.AnswerTimeField, utils.REGEXP_PREFIX) {
if dc.rsrAnswerTimeField, err = utils.NewRSRField(dc.AnswerTimeField); err != nil {
return nil, err
}
}
dc.DurationField = durFld
if strings.HasPrefix(dc.DurationField, utils.REGEXP_PREFIX) {
if dc.rsrDurationField, err = utils.NewRSRField(dc.DurationField); err != nil {
return nil, err
}
}
return dc, nil
}
type DerivedCharger struct {
RunId string // Unique runId in the chain
ReqTypeField string // Field containing request type info, number in case of csv source, '^' as prefix in case of static values
DirectionField string // Field containing direction info
TenantField string // Field containing tenant info
TorField string // Field containing tor info
AccountField string // Field containing account information
SubjectField string // Field containing subject information
DestinationField string // Field containing destination information
SetupTimeField string // Field containing setup time information
AnswerTimeField string // Field containing answer time information
DurationField string // Field containing duration information
rsrReqTypeField *utils.RSRField // Storage for compiled Regexp in case of RSRFields
rsrDirectionField *utils.RSRField
rsrTenantField *utils.RSRField
rsrTorField *utils.RSRField
rsrAccountField *utils.RSRField
rsrSubjectField *utils.RSRField
rsrDestinationField *utils.RSRField
rsrSetupTimeField *utils.RSRField
rsrAnswerTimeField *utils.RSRField
rsrDurationField *utils.RSRField
}
type DerivedChargers []*DerivedCharger
// Precheck that RunId is unique
func (dcs DerivedChargers) Append(dc *DerivedCharger) (DerivedChargers, error) {
if dc.RunId == utils.DEFAULT_RUNID {
return nil, errors.New("Reserved RunId")
}
for _, dcLocal := range dcs {
if dcLocal.RunId == dc.RunId {
return nil, errors.New("Duplicated RunId")
}
}
return append(dcs, dc), nil
}
// Parse the configuration file and returns DerivedChargers instance if no errors
func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs DerivedChargers, err error) {
var runIds, reqTypeFlds, directionFlds, tenantFlds, torFlds, acntFlds, subjFlds, dstFlds, sTimeFlds, aTimeFlds, durFlds []string
cfgVal, _ := c.GetString("derived_charging", "run_ids")
if runIds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "reqtype_fields")
if reqTypeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "direction_fields")
if directionFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "tenant_fields")
if tenantFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "tor_fields")
if torFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "account_fields")
if acntFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "subject_fields")
if subjFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "destination_fields")
if dstFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "setup_time_fields")
if sTimeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "answer_time_fields")
if aTimeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "duration_fields")
if durFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
// We need all to be the same length
if len(reqTypeFlds) != len(runIds) ||
len(directionFlds) != len(runIds) ||
len(tenantFlds) != len(runIds) ||
len(torFlds) != len(runIds) ||
len(acntFlds) != len(runIds) ||
len(subjFlds) != len(runIds) ||
len(dstFlds) != len(runIds) ||
len(sTimeFlds) != len(runIds) ||
len(aTimeFlds) != len(runIds) ||
len(durFlds) != len(runIds) {
return nil, errors.New("<ConfigSanity> Inconsistent fields length in derivated_charging section")
}
// Create the individual chargers and append them to the final instance
dcs = make(DerivedChargers, 0)
for runIdx, runId := range runIds {
dc, err := NewDerivedCharger(runId, reqTypeFlds[runIdx], directionFlds[runIdx], tenantFlds[runIdx], torFlds[runIdx],
acntFlds[runIdx], subjFlds[runIdx], dstFlds[runIdx], sTimeFlds[runIdx], aTimeFlds[runIdx], durFlds[runIdx])
if err != nil {
return nil, err
}
if dcs, err = dcs.Append(dc); err != nil {
return nil, err
}
}
return dcs, nil
}

View File

@@ -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
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package config
import (
"code.google.com/p/goconf/conf"
"errors"
"strings"
@@ -60,3 +61,78 @@ func ParseRSRFields(configVal string) ([]*utils.RSRField, error) {
}
return rsrFields, nil
}
// Parse the configuration file and returns utils.DerivedChargers instance if no errors
func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err error) {
var runIds, reqTypeFlds, directionFlds, tenantFlds, torFlds, acntFlds, subjFlds, dstFlds, sTimeFlds, aTimeFlds, durFlds []string
cfgVal, _ := c.GetString("derived_charging", "run_ids")
if runIds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "reqtype_fields")
if reqTypeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "direction_fields")
if directionFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "tenant_fields")
if tenantFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "tor_fields")
if torFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "account_fields")
if acntFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "subject_fields")
if subjFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "destination_fields")
if dstFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "setup_time_fields")
if sTimeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "answer_time_fields")
if aTimeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "duration_fields")
if durFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
// We need all to be the same length
if len(reqTypeFlds) != len(runIds) ||
len(directionFlds) != len(runIds) ||
len(tenantFlds) != len(runIds) ||
len(torFlds) != len(runIds) ||
len(acntFlds) != len(runIds) ||
len(subjFlds) != len(runIds) ||
len(dstFlds) != len(runIds) ||
len(sTimeFlds) != len(runIds) ||
len(aTimeFlds) != len(runIds) ||
len(durFlds) != len(runIds) {
return nil, errors.New("<ConfigSanity> Inconsistent fields length in derivated_charging section")
}
// Create the individual chargers and append them to the final instance
dcs = make(utils.DerivedChargers, 0)
for runIdx, runId := range runIds {
dc, err := utils.NewDerivedCharger(runId, reqTypeFlds[runIdx], directionFlds[runIdx], tenantFlds[runIdx], torFlds[runIdx],
acntFlds[runIdx], subjFlds[runIdx], dstFlds[runIdx], sTimeFlds[runIdx], aTimeFlds[runIdx], durFlds[runIdx])
if err != nil {
return nil, err
}
if dcs, err = dcs.Append(dc); err != nil {
return nil, err
}
}
return dcs, nil
}

View File

@@ -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
@@ -37,3 +37,29 @@ func TestParseRSRFields(t *testing.T) {
t.Errorf("Unexpected value of parsed fields")
}
}
func TestParseCfgDerivedCharging(t *testing.T) {
eFieldsCfg := []byte(`[derived_charging]
run_ids = run1, run2
reqtype_fields = test1, test2
direction_fields = test1, test2
tenant_fields = test1, test2
tor_fields = test1, test2
account_fields = test1, test2
subject_fields = test1, test2
destination_fields = test1, test2
setup_time_fields = test1, test2
answer_time_fields = test1, test2
duration_fields = test1, test2
`)
edcs := utils.DerivedChargers{
&utils.DerivedCharger{RunId: "run1", ReqTypeField: "test1", DirectionField: "test1", TenantField: "test1", TorField: "test1",
AccountField: "test1", SubjectField: "test1", DestinationField: "test1", SetupTimeField: "test1", AnswerTimeField: "test1", DurationField: "test1"},
&utils.DerivedCharger{RunId: "run2", ReqTypeField: "test2", DirectionField: "test2", TenantField: "test2", TorField: "test2",
AccountField: "test2", SubjectField: "test2", DestinationField: "test2", SetupTimeField: "test2", AnswerTimeField: "test2", DurationField: "test2"}}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.DerivedChargers, edcs) {
t.Errorf("Expecting: %v, received: %v", edcs, cfg.DerivedChargers)
}
}

View File

@@ -107,6 +107,7 @@ destination_fields = test # Name of destination fields to be used during additio
setup_time_fields = test # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
answer_time_fields = test # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
duration_fields = test # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.
[history_server]
enabled = true # Starts History service: <true|false>.

View File

@@ -1,8 +1,8 @@
# CGRateS Configuration file
# Real-time Charging System for Telecom & ISP environments
# Copyright (C) 2012-2014 ITsysCOM GmbH
#
# This file contains the default configuration hardcoded into CGRateS.
# This is what you get when you load CGRateS with an empty configuration file.
# [global] must exist in all files, rest of the configuration is inter-changeable.
[global]
# ratingdb_type = redis # Rating subsystem database: <redis>.
@@ -58,7 +58,6 @@
# 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
# Exported fields template <""|fld1,fld2|*xml:instance_name>
[cdrc]
# enabled = false # Enable CDR client functionality
# cdrs = internal # Address where to reach CDR server. <internal|127.0.0.1:2080>
@@ -111,6 +110,7 @@
# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
# duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
# combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.
[history_server]
# enabled = false # Starts History service: <true|false>.

View File

@@ -27,7 +27,6 @@ import (
"strconv"
"strings"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
@@ -51,7 +50,7 @@ type CSVReader struct {
ratingPlans map[string]*RatingPlan
ratingProfiles map[string]*RatingProfile
sharedGroups map[string]*SharedGroup
derivedChargers map[string]config.DerivedChargers
derivedChargers map[string]utils.DerivedChargers
// file names
destinationsFn, ratesFn, destinationratesFn, timingsFn, destinationratetimingsFn, ratingprofilesFn,
sharedgroupsFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn, derivedChargersFn string
@@ -74,7 +73,7 @@ func NewFileCSVReader(dataStorage RatingStorage, accountingStorage AccountingSto
c.ratingPlans = make(map[string]*RatingPlan)
c.ratingProfiles = make(map[string]*RatingProfile)
c.sharedGroups = make(map[string]*SharedGroup)
c.derivedChargers = make(map[string]config.DerivedChargers)
c.derivedChargers = make(map[string]utils.DerivedChargers)
c.readerFunc = openFileCSVReader
c.rpAliases = make(map[string]string)
c.accAliases = make(map[string]string)
@@ -753,10 +752,10 @@ func (csvr *CSVReader) LoadDerivedChargers() (err error) {
defer fp.Close()
}
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
tag := utils.ConcatenatedKey(record[0], record[1], record[2], record[3], record[4])
tag := utils.DerivedChargersKey(record[0], record[1], record[2], record[3], record[4])
_, found := csvr.derivedChargers[tag]
if found {
if csvr.derivedChargers[tag], err = csvr.derivedChargers[tag].Append(&config.DerivedCharger{
if csvr.derivedChargers[tag], err = csvr.derivedChargers[tag].Append(&utils.DerivedCharger{
RunId: ValueOrDefault(record[5], "*default"),
ReqTypeField: ValueOrDefault(record[6], "*default"),
DirectionField: ValueOrDefault(record[7], "*default"),
@@ -775,7 +774,7 @@ func (csvr *CSVReader) LoadDerivedChargers() (err error) {
if record[5] == utils.DEFAULT_RUNID {
return errors.New("Reserved RunId")
}
csvr.derivedChargers[tag] = config.DerivedChargers{&config.DerivedCharger{
csvr.derivedChargers[tag] = utils.DerivedChargers{&utils.DerivedCharger{
RunId: ValueOrDefault(record[5], "*default"),
ReqTypeField: ValueOrDefault(record[6], "*default"),
DirectionField: ValueOrDefault(record[7], "*default"),

View File

@@ -23,7 +23,6 @@ import (
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
@@ -834,10 +833,10 @@ func TestLoadDerivedChargers(t *testing.T) {
if len(csvr.derivedChargers) != 2 {
t.Error("Failed to load derivedChargers: ", csvr.derivedChargers)
}
expCharger1 := config.DerivedChargers{
&config.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", TorField: "*default",
expCharger1 := utils.DerivedChargers{
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", TorField: "*default",
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
&config.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", TorField: "*default",
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", TorField: "*default",
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
}
keyCharger1 := utils.ConcatenatedKey("cgrates.org", "call", "*out", "dan", "dan")

View File

@@ -24,7 +24,6 @@ import (
"log"
"strings"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
@@ -48,7 +47,7 @@ type DbReader struct {
ratingPlans map[string]*RatingPlan
ratingProfiles map[string]*RatingProfile
sharedGroups map[string]*SharedGroup
derivedChargers map[string]config.DerivedChargers
derivedChargers map[string]utils.DerivedChargers
}
func NewDbReader(storDB LoadStorage, ratingDb RatingStorage, accountDb AccountingStorage, tpid string) *DbReader {
@@ -66,7 +65,7 @@ func NewDbReader(storDB LoadStorage, ratingDb RatingStorage, accountDb Accountin
c.rpAliases = make(map[string]string)
c.accAliases = make(map[string]string)
c.accountActions = make(map[string]*Account)
c.derivedChargers = make(map[string]config.DerivedChargers)
c.derivedChargers = make(map[string]utils.DerivedChargers)
return c
}

View File

@@ -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
@@ -28,6 +28,8 @@ import (
"time"
"github.com/cgrates/cgrates/balancer2go"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
type Responder struct {
@@ -114,6 +116,17 @@ func (rs *Responder) GetMaxSessionTime(arg CallDescriptor, reply *float64) (err
return
}
func (rs *Responder) DerivedChargers(attrs utils.AttrDerivedChargers, dcs *utils.DerivedChargers) error {
// ToDo: Make it work with balancer if needed
if dcsH, err := HandleGetDerivedChargers(accountingStorage, config.CgrConfig(), attrs); err != nil {
return err
} else if dcsH != nil {
*dcs = dcsH
}
return nil
}
func (rs *Responder) FlushCache(arg CallDescriptor, reply *float64) (err error) {
if rs.Bal != nil {
*reply, err = rs.callMethod(&arg, "Responder.FlushCache")
@@ -319,6 +332,7 @@ type Connector interface {
MaxDebit(CallDescriptor, *CallCost) error
RefundIncrements(CallDescriptor, *float64) error
GetMaxSessionTime(CallDescriptor, *float64) error
DerivedChargers(utils.AttrDerivedChargers, utils.DerivedChargers) error
}
type RPCClientConnector struct {
@@ -344,3 +358,7 @@ func (rcc *RPCClientConnector) RefundIncrements(cd CallDescriptor, resp *float64
func (rcc *RPCClientConnector) GetMaxSessionTime(cd CallDescriptor, resp *float64) error {
return rcc.Client.Call("Responder.GetMaxSessionTime", cd, resp)
}
func (rcc *RPCClientConnector) DerivedChargers(attrs utils.AttrDerivedChargers, dcs utils.DerivedChargers) error {
return rcc.Client.Call("ApierV1.DerivedChargers", attrs, dcs)
}

View File

@@ -23,7 +23,6 @@ import (
"encoding/gob"
"encoding/json"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
"github.com/ugorji/go/codec"
"labix.org/v2/mgo/bson"
@@ -102,8 +101,8 @@ type AccountingStorage interface {
GetActionTimings(string) (ActionPlan, error)
SetActionTimings(string, ActionPlan) error
GetAllActionTimings() (map[string]ActionPlan, error)
GetDerivedChargers(string, bool) (config.DerivedChargers, error)
SetDerivedChargers(string, config.DerivedChargers) error
GetDerivedChargers(string, bool) (utils.DerivedChargers, error)
SetDerivedChargers(string, utils.DerivedChargers) error
}
type CdrStorage interface {

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriems 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
@@ -29,7 +29,6 @@ import (
"time"
"github.com/cgrates/cgrates/cache2go"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
@@ -438,10 +437,10 @@ func (ms *MapStorage) GetAllActionTimings() (ats map[string]ActionPlan, err erro
return
}
func (ms *MapStorage) GetDerivedChargers(key string, checkDb bool) (dcs config.DerivedChargers, err error) {
func (ms *MapStorage) GetDerivedChargers(key string, checkDb bool) (dcs utils.DerivedChargers, err error) {
key = DERIVEDCHARGERS_PREFIX + key
if x, err := cache2go.GetCached(key); err == nil {
return x.(config.DerivedChargers), nil
return x.(utils.DerivedChargers), nil
}
if !checkDb {
return nil, errors.New(utils.ERR_NOT_FOUND)
@@ -455,7 +454,7 @@ func (ms *MapStorage) GetDerivedChargers(key string, checkDb bool) (dcs config.D
return
}
func (ms *MapStorage) SetDerivedChargers(key string, dcs config.DerivedChargers) error {
func (ms *MapStorage) SetDerivedChargers(key string, dcs utils.DerivedChargers) error {
result, err := ms.ms.Marshal(dcs)
ms.dict[DERIVEDCHARGERS_PREFIX+key] = result
return err

View File

@@ -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
@@ -25,7 +25,6 @@ import (
"fmt"
"github.com/cgrates/cgrates/cache2go"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
"github.com/hoisie/redis"
@@ -544,10 +543,10 @@ func (rs *RedisStorage) GetAllActionTimings() (ats map[string]ActionPlan, err er
return
}
func (rs *RedisStorage) GetDerivedChargers(key string, checkDb bool) (dcs config.DerivedChargers, err error) {
func (rs *RedisStorage) GetDerivedChargers(key string, checkDb bool) (dcs utils.DerivedChargers, err error) {
key = DERIVEDCHARGERS_PREFIX + key
if x, err := cache2go.GetCached(key); err == nil {
return x.(config.DerivedChargers), nil
return x.(utils.DerivedChargers), nil
}
if !checkDb {
return nil, errors.New(utils.ERR_NOT_FOUND)
@@ -560,7 +559,7 @@ func (rs *RedisStorage) GetDerivedChargers(key string, checkDb bool) (dcs config
return dcs, err
}
func (rs *RedisStorage) SetDerivedChargers(key string, dcs config.DerivedChargers) (err error) {
func (rs *RedisStorage) SetDerivedChargers(key string, dcs utils.DerivedChargers) (err error) {
if len(dcs) == 0 {
_, err = rs.db.Del(DERIVEDCHARGERS_PREFIX + key)
return err

View File

@@ -56,10 +56,10 @@ func TestSetGetDerivedCharges(t *testing.T) {
return
}
keyCharger1 := utils.ConcatenatedKey("cgrates.org", "call", "*out", "dan", "dan")
charger1 := config.DerivedChargers{
&config.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", TorField: "*default",
charger1 := utils.DerivedChargers{
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", TorField: "*default",
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
&config.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", TorField: "*default",
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", TorField: "*default",
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", DurationField: "*default"},
}
if err := rds.SetDerivedChargers(keyCharger1, charger1); err != nil {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -377,3 +377,7 @@ type AttrLoadTpFromFolder struct {
type AttrGetDestination struct {
Id string
}
type AttrDerivedChargers struct {
Tenant, Tor, Direction, Account, Subject string
}

136
utils/derivedchargers.go Normal file
View File

@@ -0,0 +1,136 @@
/*
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 utils
import (
"errors"
"strings"
)
// Wraps regexp compiling in case of rsr fields
func NewDerivedCharger(runId, reqTypeFld, dirFld, tenantFld, totFld, acntFld, subjFld, dstFld, sTimeFld, aTimeFld, durFld string) (dc *DerivedCharger, err error) {
if len(runId) == 0 {
return nil, errors.New("Empty run id field")
}
dc = &DerivedCharger{RunId: runId}
dc.ReqTypeField = reqTypeFld
if strings.HasPrefix(dc.ReqTypeField, REGEXP_PREFIX) {
if dc.rsrReqTypeField, err = NewRSRField(dc.ReqTypeField); err != nil {
return nil, err
}
}
dc.DirectionField = dirFld
if strings.HasPrefix(dc.DirectionField, REGEXP_PREFIX) {
if dc.rsrDirectionField, err = NewRSRField(dc.DirectionField); err != nil {
return nil, err
}
}
dc.TenantField = tenantFld
if strings.HasPrefix(dc.TenantField, REGEXP_PREFIX) {
if dc.rsrTenantField, err = NewRSRField(dc.TenantField); err != nil {
return nil, err
}
}
dc.TorField = totFld
if strings.HasPrefix(dc.TorField, REGEXP_PREFIX) {
if dc.rsrTorField, err = NewRSRField(dc.TorField); err != nil {
return nil, err
}
}
dc.AccountField = acntFld
if strings.HasPrefix(dc.AccountField, REGEXP_PREFIX) {
if dc.rsrAccountField, err = NewRSRField(dc.AccountField); err != nil {
return nil, err
}
}
dc.SubjectField = subjFld
if strings.HasPrefix(dc.SubjectField, REGEXP_PREFIX) {
if dc.rsrSubjectField, err = NewRSRField(dc.SubjectField); err != nil {
return nil, err
}
}
dc.DestinationField = dstFld
if strings.HasPrefix(dc.DestinationField, REGEXP_PREFIX) {
if dc.rsrDestinationField, err = NewRSRField(dc.DestinationField); err != nil {
return nil, err
}
}
dc.SetupTimeField = sTimeFld
if strings.HasPrefix(dc.SetupTimeField, REGEXP_PREFIX) {
if dc.rsrSetupTimeField, err = NewRSRField(dc.SetupTimeField); err != nil {
return nil, err
}
}
dc.AnswerTimeField = aTimeFld
if strings.HasPrefix(dc.AnswerTimeField, REGEXP_PREFIX) {
if dc.rsrAnswerTimeField, err = NewRSRField(dc.AnswerTimeField); err != nil {
return nil, err
}
}
dc.DurationField = durFld
if strings.HasPrefix(dc.DurationField, REGEXP_PREFIX) {
if dc.rsrDurationField, err = NewRSRField(dc.DurationField); err != nil {
return nil, err
}
}
return dc, nil
}
type DerivedCharger struct {
RunId string // Unique runId in the chain
ReqTypeField string // Field containing request type info, number in case of csv source, '^' as prefix in case of static values
DirectionField string // Field containing direction info
TenantField string // Field containing tenant info
TorField string // Field containing tor info
AccountField string // Field containing account information
SubjectField string // Field containing subject information
DestinationField string // Field containing destination information
SetupTimeField string // Field containing setup time information
AnswerTimeField string // Field containing answer time information
DurationField string // Field containing duration information
rsrReqTypeField *RSRField // Storage for compiled Regexp in case of RSRFields
rsrDirectionField *RSRField
rsrTenantField *RSRField
rsrTorField *RSRField
rsrAccountField *RSRField
rsrSubjectField *RSRField
rsrDestinationField *RSRField
rsrSetupTimeField *RSRField
rsrAnswerTimeField *RSRField
rsrDurationField *RSRField
}
func DerivedChargersKey(tenant, tor, direction, account, subject string) string {
return ConcatenatedKey(tenant, tor, direction, account, subject)
}
type DerivedChargers []*DerivedCharger
// Precheck that RunId is unique
func (dcs DerivedChargers) Append(dc *DerivedCharger) (DerivedChargers, error) {
if dc.RunId == DEFAULT_RUNID {
return nil, errors.New("Reserved RunId")
}
for _, dcLocal := range dcs {
if dcLocal.RunId == dc.RunId {
return nil, errors.New("Duplicated RunId")
}
}
return append(dcs, dc), nil
}

View File

@@ -1,5 +1,5 @@
/*
Rating system for Telecom Environments
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
@@ -16,10 +16,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
package utils
import (
"github.com/cgrates/cgrates/utils"
"reflect"
"testing"
)
@@ -27,7 +26,7 @@ import (
func TestAppendDerivedChargers(t *testing.T) {
var err error
dcs := make(DerivedChargers, 0)
if _, err := dcs.Append(&DerivedCharger{RunId: utils.DEFAULT_RUNID}); err == nil {
if _, err := dcs.Append(&DerivedCharger{RunId: DEFAULT_RUNID}); err == nil {
t.Error("Failed to detect using of the default runid")
}
if dcs, err = dcs.Append(&DerivedCharger{RunId: "FIRST_RUNID"}); err != nil {
@@ -78,16 +77,16 @@ func TestNewDerivedCharger(t *testing.T) {
AnswerTimeField: "~answertime2:s/sip:(.+)/$1/",
DurationField: "~duration2:s/sip:(.+)/$1/",
}
edc2.rsrReqTypeField, _ = utils.NewRSRField("~reqtype2:s/sip:(.+)/$1/")
edc2.rsrDirectionField, _ = utils.NewRSRField("~direction2:s/sip:(.+)/$1/")
edc2.rsrTenantField, _ = utils.NewRSRField("~tenant2:s/sip:(.+)/$1/")
edc2.rsrTorField, _ = utils.NewRSRField("~tor2:s/sip:(.+)/$1/")
edc2.rsrAccountField, _ = utils.NewRSRField("~account2:s/sip:(.+)/$1/")
edc2.rsrSubjectField, _ = utils.NewRSRField("~subject2:s/sip:(.+)/$1/")
edc2.rsrDestinationField, _ = utils.NewRSRField("~destination2:s/sip:(.+)/$1/")
edc2.rsrSetupTimeField, _ = utils.NewRSRField("~setuptime2:s/sip:(.+)/$1/")
edc2.rsrAnswerTimeField, _ = utils.NewRSRField("~answertime2:s/sip:(.+)/$1/")
edc2.rsrDurationField, _ = utils.NewRSRField("~duration2:s/sip:(.+)/$1/")
edc2.rsrReqTypeField, _ = NewRSRField("~reqtype2:s/sip:(.+)/$1/")
edc2.rsrDirectionField, _ = NewRSRField("~direction2:s/sip:(.+)/$1/")
edc2.rsrTenantField, _ = NewRSRField("~tenant2:s/sip:(.+)/$1/")
edc2.rsrTorField, _ = NewRSRField("~tor2:s/sip:(.+)/$1/")
edc2.rsrAccountField, _ = NewRSRField("~account2:s/sip:(.+)/$1/")
edc2.rsrSubjectField, _ = NewRSRField("~subject2:s/sip:(.+)/$1/")
edc2.rsrDestinationField, _ = NewRSRField("~destination2:s/sip:(.+)/$1/")
edc2.rsrSetupTimeField, _ = NewRSRField("~setuptime2:s/sip:(.+)/$1/")
edc2.rsrAnswerTimeField, _ = NewRSRField("~answertime2:s/sip:(.+)/$1/")
edc2.rsrDurationField, _ = NewRSRField("~duration2:s/sip:(.+)/$1/")
if dc2, err := NewDerivedCharger("test2",
"~reqtype2:s/sip:(.+)/$1/",
"~direction2:s/sip:(.+)/$1/",
@@ -105,28 +104,8 @@ func TestNewDerivedCharger(t *testing.T) {
}
}
func TestParseCfgDerivedCharging(t *testing.T) {
eFieldsCfg := []byte(`[derived_charging]
run_ids = run1, run2
reqtype_fields = test1, test2
direction_fields = test1, test2
tenant_fields = test1, test2
tor_fields = test1, test2
account_fields = test1, test2
subject_fields = test1, test2
destination_fields = test1, test2
setup_time_fields = test1, test2
answer_time_fields = test1, test2
duration_fields = test1, test2
`)
edcs := DerivedChargers{
&DerivedCharger{RunId: "run1", ReqTypeField: "test1", DirectionField: "test1", TenantField: "test1", TorField: "test1",
AccountField: "test1", SubjectField: "test1", DestinationField: "test1", SetupTimeField: "test1", AnswerTimeField: "test1", DurationField: "test1"},
&DerivedCharger{RunId: "run2", ReqTypeField: "test2", DirectionField: "test2", TenantField: "test2", TorField: "test2",
AccountField: "test2", SubjectField: "test2", DestinationField: "test2", SetupTimeField: "test2", AnswerTimeField: "test2", DurationField: "test2"}}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.DerivedChargers, edcs) {
t.Errorf("Expecting: %v, received: %v", edcs, cfg.DerivedChargers)
func TestDerivedChargersKey(t *testing.T) {
if dcKey := DerivedChargersKey("cgrates.org", "call", "*out", "dan", "dan"); dcKey != "cgrates.org:call:*out:dan:dan" {
t.Error("Unexpected derived chargers key: ", dcKey)
}
}

View File

@@ -34,6 +34,9 @@ func (rsr *ReSearchReplace) Process(source string) string {
}
res := []byte{}
match := rsr.SearchRegexp.FindStringSubmatchIndex(source)
if match == nil {
return source // No match returns unaltered source, so we can play with national vs international dialing
}
res = rsr.SearchRegexp.ExpandString(res, rsr.ReplaceTemplate, source, match)
return string(res)
}

View File

@@ -49,3 +49,13 @@ func TestProcessReSearchReplace3(t *testing.T) { //"MatchedDestId":"CST_31800_DE
t.Error("Unexpected output from SearchReplace: ", outStr)
}
}
func TestProcessReSearchReplace4(t *testing.T) {
rsr := &ReSearchReplace{regexp.MustCompile(`^\+49(\d+)`), "0$1"}
if outStr := rsr.Process("+4986517174963"); outStr != "086517174963" {
t.Error("Unexpected output from SearchReplace: ", outStr)
}
if outStr := rsr.Process("+186517174963"); outStr != "+186517174963" {
t.Error("Unexpected output from SearchReplace: ", outStr)
}
}

View File

@@ -199,6 +199,9 @@ func TestFormatCost(t *testing.T) {
if cdr.FormatCost(2, 0) != "101" {
t.Error("Unexpected format of the cost: ", cdr.FormatCost(2, 0))
}
if cdr.FormatCost(1, 0) != "10" {
t.Error("Unexpected format of the cost: ", cdr.FormatCost(1, 0))
}
if cdr.FormatCost(2, 3) != "101.001" {
t.Error("Unexpected format of the cost: ", cdr.FormatCost(2, 3))
}