diff --git a/config/xmlcdrc.go b/config/xmlcdrc.go new file mode 100644 index 000000000..ea2ad43b9 --- /dev/null +++ b/config/xmlcdrc.go @@ -0,0 +1,50 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) 2012-2014 ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package config + +import ( + "encoding/xml" + "github.com/cgrates/cgrates/utils" +) + +type CgrXmlCdrcCfg struct { + Enabled bool `xml:"enabled"` // Enable/Disable the + CdrsAddress string `xml:"cdrs_address"` // The address where CDRs can be reached + CdrsMethod string `xml:"cdrs_method"` // Method to use when posting CDRs + CdrType string `xml:"cdr_type"` // The type of CDR to process + RunDelay int64 `xml:"run_delay"` // Delay between runs + CdrInDir string `xml:"cdr_in_dir"` // Folder to process CDRs from + CdrOutDir string `xml:"cdr_out_dir"` // Folder to move processed CDRs to + CdrSourceId string `xml:"cdr_source_id"` // Source identifier for the processed CDRs + CdrFields []CdrcField `xml:"fields>field"` +} + +type CdrcField struct { + XMLName xml.Name `xml:"field"` + Id string `xml:"id,attr"` + Filter string `xml:"filter,attr"` + RSRField *utils.RSRField +} + +func (cdrcFld *CdrcField) PopulateRSRFIeld() (err error) { + if cdrcFld.RSRField, err = utils.NewRSRField(cdrcFld.Filter); err != nil { + return err + } + return nil +} diff --git a/config/xmlcdrc_test.go b/config/xmlcdrc_test.go new file mode 100644 index 000000000..140eb2d1a --- /dev/null +++ b/config/xmlcdrc_test.go @@ -0,0 +1,116 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) 2012-2014 ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package config + +import ( + "encoding/xml" + "github.com/cgrates/cgrates/utils" + "reflect" + "strings" + "testing" +) + +var cfgDocCdrc *CgrXmlCfgDocument // Will be populated by first test + +func TestPopulateRSRFIeld(t *testing.T) { + cdrcField := CdrcField{Id: "TEST1", Filter: `~effective_caller_id_number:s/(\d+)/+$1/`} + if err := cdrcField.PopulateRSRFIeld(); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if cdrcField.RSRField == nil { + t.Error("Failed loading the RSRField") + } + cdrcField = CdrcField{Id: "TEST2", Filter: `1`} + if err := cdrcField.PopulateRSRFIeld(); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if cdrcField.RSRField == nil { + t.Error("Failed loading the RSRField") + } +} + +func TestParseXmlCdrcConfig(t *testing.T) { + cfgXmlStr := ` + + + true + internal + http_cgr + csv + 0 + /var/log/cgrates/cdrc/in + /var/log/cgrates/cdrc/out + freeswitch_csv + + + + + + + + + + + + + + + + +` + var err error + reader := strings.NewReader(cfgXmlStr) + if cfgDocCdrc, err = ParseCgrXmlConfig(reader); err != nil { + t.Error(err.Error()) + } else if cfgDocCdrc == nil { + t.Fatal("Could not parse xml configuration document") + } + if len(cfgDocCdrc.cdrcs) != 1 { + t.Error("Did not cache") + } +} + +func TestGetCdrcCfg(t *testing.T) { + cdrcfg, err := cfgDocCdrc.GetCdrcCfg("CDRC-CSV1") + if err != nil { + t.Error("Unexpected error: ", err) + } else if cdrcfg == nil { + t.Error("No config instance returned") + } + expectCdrc := &CgrXmlCdrcCfg{Enabled: true, CdrsAddress: "internal", CdrsMethod: "http_cgr", CdrType: "csv", + RunDelay: 0, CdrInDir: "/var/log/cgrates/cdrc/in", CdrOutDir: "/var/log/cgrates/cdrc/out", CdrSourceId: "freeswitch_csv"} + cdrFlds := []CdrcField{CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCID, Filter: "0"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.REQTYPE, Filter: "1"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DIRECTION, Filter: "2"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.TENANT, Filter: "3"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.CATEGORY, Filter: "4"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCOUNT, Filter: "5"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.SUBJECT, Filter: "6"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DESTINATION, Filter: "7"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.SETUP_TIME, Filter: "8"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ANSWER_TIME, Filter: "9"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.USAGE, Filter: "10"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr1", Filter: "11"}, + CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr2", Filter: "12"}} + for _, fld := range cdrFlds { + fld.PopulateRSRFIeld() + } + expectCdrc.CdrFields = cdrFlds + if !reflect.DeepEqual(expectCdrc, cdrcfg) { + t.Errorf("Expecting: %v, received: %v", expectCdrc, cdrcfg) + } +} diff --git a/config/xmlcdre.go b/config/xmlcdre.go new file mode 100644 index 000000000..a1a68da82 --- /dev/null +++ b/config/xmlcdre.go @@ -0,0 +1,61 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) 2012-2014 ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package config + +import ( + "encoding/xml" +) + +// The CdrExporter Fixed Width configuration instance +type CgrXmlCdreFwCfg struct { + Header *CgrXmlCfgCdrHeader `xml:"header"` + Content *CgrXmlCfgCdrContent `xml:"content"` + Trailer *CgrXmlCfgCdrTrailer `xml:"trailer"` +} + +// CDR header +type CgrXmlCfgCdrHeader struct { + XMLName xml.Name `xml:"header"` + Fields []*CgrXmlCfgCdrField `xml:"fields>field"` +} + +// CDR content +type CgrXmlCfgCdrContent struct { + XMLName xml.Name `xml:"content"` + Fields []*CgrXmlCfgCdrField `xml:"fields>field"` +} + +// CDR trailer +type CgrXmlCfgCdrTrailer struct { + XMLName xml.Name `xml:"trailer"` + Fields []*CgrXmlCfgCdrField `xml:"fields>field"` +} + +// CDR field +type CgrXmlCfgCdrField struct { + XMLName xml.Name `xml:"field"` + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` + Value string `xml:"value,attr"` + Width int `xml:"width,attr"` // Field width + Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright> + Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right> + Layout string `xml:"layout,attr"` // Eg. time format layout + Mandatory bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported +} diff --git a/config/xmlconfig_test.go b/config/xmlcdre_test.go similarity index 93% rename from config/xmlconfig_test.go rename to config/xmlcdre_test.go index 922747a6a..7840b402a 100644 --- a/config/xmlconfig_test.go +++ b/config/xmlcdre_test.go @@ -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 @@ -87,15 +87,7 @@ func TestParseXmlConfig(t *testing.T) { } else if cfgDoc == nil { t.Fatal("Could not parse xml configuration document") } -} - -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 { + if len(cfgDoc.cdrefws) != 1 { t.Error("Did not cache") } } diff --git a/config/xmlconfig.go b/config/xmlconfig.go index 289375620..585b30587 100644 --- a/config/xmlconfig.go +++ b/config/xmlconfig.go @@ -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 @@ -32,6 +32,9 @@ func ParseCgrXmlConfig(reader io.Reader) (*CgrXmlCfgDocument, error) { if err := decoder.Decode(xmlConfig); err != nil { return nil, err } + if err := xmlConfig.cacheAll(); err != nil { + return nil, err + } return xmlConfig, nil } @@ -41,6 +44,7 @@ type CgrXmlCfgDocument struct { 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 + cdrcs map[string]*CgrXmlCdrcCfg } // Storage for raw configuration @@ -52,42 +56,19 @@ type CgrXmlConfiguration struct { RawConfig []byte `xml:",innerxml"` // Used to store the configuration struct, as raw so we can store different types } -// The CdrExporter Fixed Width configuration instance -type CgrXmlCdreFwCfg struct { - Header *CgrXmlCfgCdrHeader `xml:"header"` - Content *CgrXmlCfgCdrContent `xml:"content"` - Trailer *CgrXmlCfgCdrTrailer `xml:"trailer"` +func (cfgInst *CgrXmlConfiguration) rawConfigElement() []byte { + rawConfig := append([]byte(""), cfgInst.RawConfig...) // Encapsulate the rawConfig in one element so we can Unmarshall into one struct + rawConfig = append(rawConfig, []byte("")...) + return rawConfig } -// CDR header -type CgrXmlCfgCdrHeader struct { - XMLName xml.Name `xml:"header"` - Fields []*CgrXmlCfgCdrField `xml:"fields>field"` -} - -// CDR content -type CgrXmlCfgCdrContent struct { - XMLName xml.Name `xml:"content"` - Fields []*CgrXmlCfgCdrField `xml:"fields>field"` -} - -// CDR trailer -type CgrXmlCfgCdrTrailer struct { - XMLName xml.Name `xml:"trailer"` - Fields []*CgrXmlCfgCdrField `xml:"fields>field"` -} - -// CDR field -type CgrXmlCfgCdrField struct { - XMLName xml.Name `xml:"field"` - Name string `xml:"name,attr"` - Type string `xml:"type,attr"` - Value string `xml:"value,attr"` - Width int `xml:"width,attr"` // Field width - Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright> - Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right> - Layout string `xml:"layout,attr"` // Eg. time format layout - Mandatory bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported +func (xmlCfg *CgrXmlCfgDocument) cacheAll() error { + for _, cacheFunc := range []func() error{xmlCfg.cacheCdreFWCfgs, xmlCfg.cacheCdrcCfgs} { + if err := cacheFunc(); err != nil { + return err + } + } + return nil } // Avoid building from raw config string always, so build cache here @@ -110,14 +91,42 @@ func (xmlCfg *CgrXmlCfgDocument) cacheCdreFWCfgs() error { 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 +// Avoid building from raw config string always, so build cache here +func (xmlCfg *CgrXmlCfgDocument) cacheCdrcCfgs() error { + xmlCfg.cdrcs = make(map[string]*CgrXmlCdrcCfg) + for _, cfgInst := range xmlCfg.Configurations { + if cfgInst.Section != utils.CDRC { + continue // Another type of config instance, not interesting to process } + cdrcCfg := new(CgrXmlCdrcCfg) + if err := xml.Unmarshal(cfgInst.rawConfigElement(), cdrcCfg); err != nil { + return err + } else if cdrcCfg == nil { + return fmt.Errorf("Could not unmarshal config instance: %s", cfgInst.Id) + } + // Cache rsr fields + for _, fld := range cdrcCfg.CdrFields { + if err := fld.PopulateRSRFIeld(); err != nil { + return fmt.Errorf("Populating field %s, error: %s", fld.Id, err.Error()) + } + } + xmlCfg.cdrcs[cfgInst.Id] = cdrcCfg } - if cfg, hasIt := xmlCfg.cdrefws[instName]; hasIt { + return nil +} + +func (xmlCfg *CgrXmlCfgDocument) GetCdreFWCfg(instName string) (*CgrXmlCdreFwCfg, error) { + if cfg, hasIt := xmlCfg.cdrefws[instName]; !hasIt { + return nil, nil + } else { + return cfg, nil + } +} + +func (xmlCfg *CgrXmlCfgDocument) GetCdrcCfg(instName string) (*CgrXmlCdrcCfg, error) { + if cfg, hasIt := xmlCfg.cdrcs[instName]; !hasIt { + return nil, nil + } else { return cfg, nil } - return nil, nil } diff --git a/utils/consts.go b/utils/consts.go index 4c3d9c77d..6dee4842c 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -100,6 +100,7 @@ const ( CDRE_FIXED_WIDTH = "fwv" XML_PROFILE_PREFIX = "*xml:" CDRE = "cdre" + CDRC = "cdrc" MASK_CHAR = "*" CONCATENATED_KEY_SEP = ":" META_DEFAULT = "*default" diff --git a/utils/rsrfield.go b/utils/rsrfield.go index aed9133cc..4af340f7f 100644 --- a/utils/rsrfield.go +++ b/utils/rsrfield.go @@ -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 diff --git a/utils/rsrfield_test.go b/utils/rsrfield_test.go index 539b319ce..fcf466540 100644 --- a/utils/rsrfield_test.go +++ b/utils/rsrfield_test.go @@ -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