Adding config xml template for exporter with fixed width CDRs together with tests

This commit is contained in:
DanB
2014-03-22 22:16:59 +01:00
parent 4045d022ae
commit 4bb3e745f1
12 changed files with 206 additions and 54 deletions

View File

@@ -413,7 +413,7 @@ func TestMaxDebit1001(t *testing.T) {
for _, blnc := range blncLst {
if blnc.SharedGroup == "SHARED_A" && blnc.Value != 5 {
t.Errorf("Unexpected value for shared balance: %f", blnc.Value)
} else if len(blnc.SharedGroup) == 0 && blnc.Value != 4.4 {
} else if len(blnc.SharedGroup) == 0 && blnc.Value != 4.7 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
}
@@ -448,9 +448,9 @@ func TestMaxDebit1007(t *testing.T) {
}
blncLst := acnt.BalanceMap["*monetary*out"]
for _, blnc := range blncLst {
if blnc.SharedGroup == "SHARED_A" && blnc.Value != 4 {
if blnc.SharedGroup == "SHARED_A" && blnc.Value != 4.7 {
t.Errorf("Unexpected value for shared balance: %f", blnc.Value)
} else if len(blnc.SharedGroup) == 0 && blnc.Value != 4.4 {
} else if len(blnc.SharedGroup) == 0 && blnc.Value != 4.7 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
}

View File

@@ -21,6 +21,8 @@ package config
import (
"errors"
"fmt"
"os"
"strings"
"time"
"code.google.com/p/goconf/conf"
@@ -59,37 +61,39 @@ type CGRConfig struct {
RatingDBUser string // The user to sign in as.
RatingDBPass string // The user's password.
AccountDBType string
AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
AccountDBPort string // The port to bind to.
AccountDBName string // The name of the database to connect to.
AccountDBUser string // The user to sign in as.
AccountDBPass string // The user's password.
StorDBType string // Should reflect the database type used to store logs
StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
StorDBPort string // Th e port to bind to.
StorDBName string // The name of the database to connect to.
StorDBUser string // The user to sign in as.
StorDBPass string // The user's password.
DBDataEncoding string // The encoding used to store object data in strings: <msgpack|json>
RPCJSONListen string // RPC JSON listening address
RPCGOBListen string // RPC GOB listening address
HTTPListen string // HTTP listening address
DefaultReqType string // Use this request type if not defined on top
DefaultTOR string // set default type of record
DefaultTenant string // set default tenant
DefaultSubject string // set default rating subject, useful in case of fallback
RoundingMethod string // Rounding method for the end price: <*up|*middle|*down>
RoundingDecimals int // Number of decimals to round end prices at
RaterEnabled bool // start standalone server (no balancer)
RaterBalancer string // balancer address host:port
AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
AccountDBPort string // The port to bind to.
AccountDBName string // The name of the database to connect to.
AccountDBUser string // The user to sign in as.
AccountDBPass string // The user's password.
StorDBType string // Should reflect the database type used to store logs
StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
StorDBPort string // Th e port to bind to.
StorDBName string // The name of the database to connect to.
StorDBUser string // The user to sign in as.
StorDBPass string // The user's password.
DBDataEncoding string // The encoding used to store object data in strings: <msgpack|json>
RPCJSONListen string // RPC JSON listening address
RPCGOBListen string // RPC GOB listening address
HTTPListen string // HTTP listening address
DefaultReqType string // Use this request type if not defined on top
DefaultTOR string // set default type of record
DefaultTenant string // set default tenant
DefaultSubject string // set default rating subject, useful in case of fallback
RoundingMethod string // Rounding method for the end price: <*up|*middle|*down>
RoundingDecimals int // Number of decimals to round end prices at
XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document
RaterEnabled bool // start standalone server (no balancer)
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>
CdreExportedFields []*utils.RSRField // List of fields in the exported CDRs
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>
@@ -184,6 +188,7 @@ func (self *CGRConfig) setDefaults() error {
self.DefaultSubject = "cgrates"
self.RoundingMethod = utils.ROUNDING_MIDDLE
self.RoundingDecimals = 4
self.XmlCfgDocument = nil
self.RaterEnabled = false
self.RaterBalancer = ""
self.BalancerEnabled = false
@@ -277,6 +282,14 @@ func (self *CGRConfig) setDefaults() error {
}
func (self *CGRConfig) checkConfigSanity() error {
// Cdre sanity check for fixed_width
if self.CdreCdrFormat == utils.FIXED_WIDTH {
if self.XmlCfgDocument == nil {
return errors.New("Need XmlConfigurationDocument for fixed_width cdr export")
} else if self.CdreFWXmlTemplate == nil {
return errors.New("Need XmlTemplate for fixed_width cdr export")
}
}
// SessionManager should have same fields config length for session emulation
if len(self.SMReqTypeFields) != len(self.SMRunIds) ||
len(self.SMDirectionFields) != len(self.SMRunIds) ||
@@ -435,6 +448,19 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("global", "rounding_decimals"); hasOpt {
cfg.RoundingDecimals, _ = c.GetInt("global", "rounding_decimals")
}
// XML config path defined, try loading the document
if hasOpt = c.HasOption("global", "xmlcfg_path"); hasOpt {
xmlCfgPath, _ := c.GetString("global", "xmlcfg_path")
xmlFile, err := os.Open(xmlCfgPath)
if err != nil {
return nil, err
}
if cgrXmlCfgDoc, err := ParseCgrXmlConfig(xmlFile); err != nil {
return nil, err
} else {
cfg.XmlCfgDocument = cgrXmlCfgDoc
}
}
if hasOpt = c.HasOption("rater", "enabled"); hasOpt {
cfg.RaterEnabled, _ = c.GetBool("rater", "enabled")
}
@@ -464,12 +490,20 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("cdre", "cdr_format"); hasOpt {
cfg.CdreCdrFormat, _ = c.GetString("cdre", "cdr_format")
}
if hasOpt = c.HasOption("cdre", "exported_fields"); hasOpt {
extraFieldsStr, _ := c.GetString("cdre", "exported_fields")
if extraFields, err := ParseRSRFields(extraFieldsStr); err != nil {
return nil, errParse
} else {
cfg.CdreExportedFields = extraFields
if hasOpt = c.HasOption("cdre", "export_template"); hasOpt { // Load configs for csv normally from template, fixed_width from xml file
exportTemplate, _ := c.GetString("cdre", "export_template")
if cfg.CdreCdrFormat != utils.FIXED_WIDTH { // Csv most likely
if extraFields, err := ParseRSRFields(exportTemplate); err != nil {
return nil, errParse
} else {
cfg.CdreExportedFields = extraFields
}
} else if strings.HasPrefix(exportTemplate, utils.XML_PROFILE_PREFIX) {
if xmlTemplate, err := cfg.XmlCfgDocument.GetCdreFWCfg(exportTemplate[len(utils.XML_PROFILE_PREFIX):]); err != nil {
return nil, err
} else {
cfg.CdreFWXmlTemplate = xmlTemplate
}
}
}
if hasOpt = c.HasOption("cdre", "export_dir"); hasOpt {

View File

@@ -0,0 +1,47 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"flag"
"path"
"testing"
)
var testLocal = flag.Bool("local", false, "Perform the tests only on local test environment, not by default.") // This flag will be passed here via "go test -local" args
var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here")
func TestLoadXmlCfg(t *testing.T) {
if !*testLocal {
return
}
cfgPath := path.Join(*dataDir, "conf", "samples", "config_local_test.cfg")
cfg, err := NewCGRConfig(&cfgPath)
if err != nil {
t.Error(err)
}
if cfg.XmlCfgDocument == nil {
t.Error("Did not load the XML Config Document")
}
if cdreFWCfg, err := cfg.XmlCfgDocument.GetCdreFWCfg("CDREFW-A"); err != nil {
t.Error(err)
} else if cdreFWCfg == nil {
t.Error("Could not retrieve CDRExporter FixedWidth config instance")
}
}

View File

@@ -73,6 +73,7 @@ func TestDefaults(t *testing.T) {
eCfg.DefaultSubject = "cgrates"
eCfg.RoundingMethod = utils.ROUNDING_MIDDLE
eCfg.RoundingDecimals = 4
eCfg.XmlCfgDocument = nil
eCfg.RaterEnabled = false
eCfg.RaterBalancer = ""
eCfg.BalancerEnabled = false
@@ -182,6 +183,11 @@ func TestSanityCheck(t *testing.T) {
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect config insanity")
}
cfg = &CGRConfig{}
cfg.CdreCdrFormat = utils.FIXED_WIDTH
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect fixed_width dependency on xml configuration")
}
}
// Load config from file and make sure we have all set

View File

@@ -50,7 +50,7 @@ mediator = test # Address where to reach the Mediator. Empty for disabling me
[cdre]
cdr_format = test # Exported CDRs format <csv>
export_dir = test # Path where the exported CDRs will be placed
exported_fields = test # List of fields in the exported CDRs
export_template = test # List of fields in the exported CDRs
[cdrc]
enabled = true # Enable CDR client functionality

View File

@@ -20,7 +20,7 @@ package config
import (
"encoding/xml"
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
"io"
)
@@ -37,9 +37,10 @@ func ParseCgrXmlConfig(reader io.Reader) (*CgrXmlCfgDocument, error) {
// Define a format for configuration file, one doc contains more configuration instances, identified by section, type and id
type CgrXmlCfgDocument struct {
XMLName xml.Name `xml:"document"`
Type string `xml:"type,attr"`
Configurations []*CgrXmlConfiguration `xml:"configuration"`
XMLName xml.Name `xml:"document"`
Type string `xml:"type,attr"`
Configurations []*CgrXmlConfiguration `xml:"configuration"`
cdrefws map[string]*CgrXmlCdreFwCfg // Cache for processed fixed width config instances, key will be the id of the instance
}
// Storage for raw configuration
@@ -48,7 +49,7 @@ type CgrXmlConfiguration struct {
Section string `xml:"section,attr"`
Type string `xml:"type,attr"`
Id string `xml:"id,attr"`
RawConfig []byte `xml:",innerxml"`
RawConfig []byte `xml:",innerxml"` // Used to store the configuration struct, as raw so we can store different types
}
// The CdrExporter Fixed Width configuration instance
@@ -84,20 +85,34 @@ type CgrXmlCfgCdrField struct {
Width string `xml:"width,attr"`
}
func (xmlCfg *CgrXmlCfgDocument) GetCdreFWCfg(instName string) (*CgrXmlCdreFwCfg, error) {
cdrefwCfg := new(CgrXmlCdreFwCfg)
// Avoid building from raw config string always, so build cache here
func (xmlCfg *CgrXmlCfgDocument) cacheCdreFWCfgs() error {
xmlCfg.cdrefws = make(map[string]*CgrXmlCdreFwCfg)
for _, cfgInst := range xmlCfg.Configurations {
if cfgInst.Section != "cdre" || cfgInst.Type != utils.CDR_FIXED_WIDTH || cfgInst.Id != instName {
continue
if cfgInst.Section == utils.CDRE || cfgInst.Type == utils.FIXED_WIDTH {
cdrefwCfg := new(CgrXmlCdreFwCfg)
rawConfig := append([]byte("<element>"), cfgInst.RawConfig...) // Encapsulate the rawConfig in one element so we can Unmarshall
rawConfig = append(rawConfig, []byte("</element>")...)
if err := xml.Unmarshal(rawConfig, cdrefwCfg); err != nil {
return err
} else if cdrefwCfg == nil {
return fmt.Errorf("Could not unmarshal CgrXmlCdreFwCfg: %s", cfgInst.Id)
} else { // All good, cache the config instance
xmlCfg.cdrefws[cfgInst.Id] = cdrefwCfg
}
}
rawConfig := append([]byte("<element>"), cfgInst.RawConfig...)
rawConfig = append(rawConfig, []byte("</element>")...)
if err := xml.Unmarshal(rawConfig, cdrefwCfg); err != nil { // Encapsulate the rawConfig in one element so we can Unmarshall
}
return nil
}
func (xmlCfg *CgrXmlCfgDocument) GetCdreFWCfg(instName string) (*CgrXmlCdreFwCfg, error) {
if len(xmlCfg.cdrefws) == 0 { // First time, cache also
if err := xmlCfg.cacheCdreFWCfgs(); err != nil {
return nil, err
} else if cdrefwCfg == nil {
return nil, errors.New("Could not unmarshal CgrXmlCdreFwCfg")
}
return cdrefwCfg, nil
}
if cfg, hasIt := xmlCfg.cdrefws[instName]; hasIt {
return cfg, nil
}
return nil, nil
}

View File

@@ -28,7 +28,7 @@ var cfgDoc *CgrXmlCfgDocument // Will be populated by first test
func TestParseXmlConfig(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8" ?>
<document type="cgrates/xml">
<configuration section="cdre" type="cdr_fixed_width" id="CDRE-FW1">
<configuration section="cdre" type="fixed_width" id="CDRE-FW1">
<header>
<fields>
<field name="RecordType" type="constant" value="10" width="2"/>
@@ -89,6 +89,17 @@ func TestParseXmlConfig(t *testing.T) {
}
}
func TestCacheCdreFWCfgs(t *testing.T) {
if len(cfgDoc.cdrefws) != 0 {
t.Error("Cache should be empty before caching")
}
if err := cfgDoc.cacheCdreFWCfgs(); err != nil {
t.Error(err)
} else if len(cfgDoc.cdrefws) != 1 {
t.Error("Did not cache")
}
}
func TestGetCdreFWCfg(t *testing.T) {
cdreFWCfg, err := cfgDoc.GetCdreFWCfg("CDRE-FW1")
if err != nil {

View File

@@ -33,6 +33,7 @@
# default_subject = cgrates # Default rating Subject to consider when missing from requests.
# rounding_method = *middle # Rounding method for floats/costs: <*up|*middle|*down>
# rounding_decimals = 4 # Number of decimals to round float/costs at
# xmlcfg_path = # Path towards additional config defined in xml file
[balancer]
# enabled = false # Start Balancer service: <true|false>.
@@ -52,8 +53,8 @@
[cdre]
# cdr_format = csv # Exported CDRs format <csv>
# export_dir = /var/log/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed
# exported_fields = cgrid,mediation_runid,accid,cdrhost,reqtype,direction,tenant,tor,account,subject,destination,setup_time,answer_time,duration,cost
# List of fields in the exported CDRs
# 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

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<document type="cgrates/xml">
<configuration section="cdre" type="fixed_width" id="CDREFW-A">
<header>
<fields>
<field name="Filler1" type="filler" width="4"/>
</fields>
</header>
<content>
<fields>
<field name="TypeOfRecord" type="constant" value="call"/>
</fields>
</content>
<trailer>
<fields>
<field name="Filler1" type="filler" width="3"/>
</fields>
</trailer>
</configuration>
</document>

View File

@@ -0,0 +1,13 @@
# CGRateS Configuration file
#
# 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]
xmlcfg_path = /usr/share/cgrates/conf/samples/cgr_addconfig.xml # Path towards additional config defined in xml file
[cdre]
cdr_format = fixed_width # Exported CDRs format <csv>
export_template = *xml:CDREFW-A # Exported fields template <""|fld1,fld2|*xml:instance_name>

View File

@@ -10,9 +10,12 @@ go test github.com/cgrates/cgrates/cdrc -local
cdrc=$?
go test github.com/cgrates/cgrates/mediator -local
med=$?
go test github.com/cgrates/cgrates/config -local
cfg=$?
exit $gen && $ap && $en && $cdrc && $med
exit $gen && $ap && $en && $cdrc && $med && $cfg

View File

@@ -91,7 +91,9 @@ const (
INTERNAL = "internal"
ZERO_RATING_SUBJECT_PREFIX = "*zero"
OK = "OK"
CDR_FIXED_WIDTH = "cdr_fixed_width"
FIXED_WIDTH = "fixed_width"
XML_PROFILE_PREFIX = "*xml:"
CDRE = "cdre"
)
var (