mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-20 06:38:45 +05:00
Use another package for xml glide updated too + add tests
This commit is contained in:
committed by
Dan Christian Bogos
parent
4f3013a6e1
commit
0a8c6f609e
69
cdrc/xml.go
69
cdrc/xml.go
@@ -19,9 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package cdrc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -29,9 +27,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ChrisTrenkamp/goxpath"
|
||||
"github.com/ChrisTrenkamp/goxpath/tree"
|
||||
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
|
||||
"github.com/beevik/etree"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
@@ -39,36 +35,17 @@ import (
|
||||
|
||||
// getElementText will process the node to extract the elementName's text out of it (only first one found)
|
||||
// returns utils.ErrNotFound if the element is not found in the node
|
||||
func elementText(xmlNode tree.Node, elmntPath string) (string, error) {
|
||||
xp, err := goxpath.Parse(elmntPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
elmntBuf := bytes.NewBufferString(xml.Header)
|
||||
if err := goxpath.Marshal(xmlNode, elmntBuf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
xmlTree, err := xmltree.ParseXML(elmntBuf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
elmnt, err := xp.ExecNode(xmlTree)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(elmnt) == 0 {
|
||||
func elementText(xmlElement *etree.Element, elmntPath string) (string, error) {
|
||||
elmnt := xmlElement.FindElement(elmntPath)
|
||||
if elmnt == nil {
|
||||
return "", utils.ErrNotFound
|
||||
}
|
||||
elem, ok := elmnt[0].(tree.Elem)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Cannot parse ")
|
||||
}
|
||||
return elem.ResValue(), nil
|
||||
return elmnt.Text(), nil
|
||||
}
|
||||
|
||||
// handlerUsageDiff will calculate the usage as difference between timeEnd and timeStart
|
||||
// Expects the 2 arguments in template separated by |
|
||||
func handlerSubstractUsage(xmlElmnt tree.Node, argsTpl config.RSRParsers, cdrPath utils.HierarchyPath, timezone string) (time.Duration, error) {
|
||||
func handlerSubstractUsage(xmlElement *etree.Element, argsTpl config.RSRParsers, cdrPath utils.HierarchyPath, timezone string) (time.Duration, error) {
|
||||
var argsStr string
|
||||
for _, rsrArg := range argsTpl {
|
||||
if rsrArg.Rules == utils.HandlerArgSep {
|
||||
@@ -76,8 +53,8 @@ func handlerSubstractUsage(xmlElmnt tree.Node, argsTpl config.RSRParsers, cdrPat
|
||||
continue
|
||||
}
|
||||
absolutePath := utils.ParseHierarchyPath(rsrArg.Rules, "")
|
||||
relPath := utils.HierarchyPath(absolutePath[len(cdrPath)-1:]) // Need relative path to the xmlElmnt
|
||||
argStr, _ := elementText(xmlElmnt, relPath.AsString("/", true))
|
||||
relPath := utils.HierarchyPath(absolutePath[len(cdrPath):]) // Need relative path to the xmlElmnt
|
||||
argStr, _ := elementText(xmlElement, relPath.AsString("/", false))
|
||||
argsStr += argStr
|
||||
}
|
||||
handlerArgs := strings.Split(argsStr, utils.HandlerArgSep)
|
||||
@@ -97,29 +74,21 @@ func handlerSubstractUsage(xmlElmnt tree.Node, argsTpl config.RSRParsers, cdrPat
|
||||
|
||||
func NewXMLRecordsProcessor(recordsReader io.Reader, cdrPath utils.HierarchyPath, timezone string,
|
||||
httpSkipTlsCheck bool, cdrcCfgs []*config.CdrcConfig, filterS *engine.FilterS) (*XMLRecordsProcessor, error) {
|
||||
xp, err := goxpath.Parse(cdrPath.AsString("/", true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
xmlDocument := etree.NewDocument() // create new document
|
||||
xmlDocument.ReadSettings.CharsetReader = func(label string, input io.Reader) (io.Reader, error) { // fix for enconding != UTF-8
|
||||
return input, nil
|
||||
}
|
||||
optsNotStrict := func(s *xmltree.ParseOptions) {
|
||||
s.Strict = false
|
||||
}
|
||||
xmlNode, err := xmltree.ParseXML(recordsReader, optsNotStrict)
|
||||
if err != nil {
|
||||
if _, err := xmlDocument.ReadFrom(recordsReader); err != nil { // read file and build xml document
|
||||
return nil, err
|
||||
}
|
||||
xmlProc := &XMLRecordsProcessor{cdrPath: cdrPath, timezone: timezone,
|
||||
httpSkipTlsCheck: httpSkipTlsCheck, cdrcCfgs: cdrcCfgs, filterS: filterS}
|
||||
prov, err := xp.ExecNode(xmlNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xmlProc.cdrXmlElmts = prov
|
||||
xmlProc.cdrXmlElmts = xmlDocument.Root().FindElements(cdrPath.AsString("/", true))
|
||||
return xmlProc, nil
|
||||
}
|
||||
|
||||
type XMLRecordsProcessor struct {
|
||||
cdrXmlElmts tree.NodeSet // result of splitting the XML doc into CDR elements
|
||||
cdrXmlElmts []*etree.Element // result of splitting the XML doc into CDR elements
|
||||
procItems int // current number of processed records from file
|
||||
cdrPath utils.HierarchyPath // path towards one CDR element
|
||||
timezone string
|
||||
@@ -163,7 +132,7 @@ func (xmlProc *XMLRecordsProcessor) ProcessNextRecord() (cdrs []*engine.CDR, err
|
||||
return cdrs, nil
|
||||
}
|
||||
|
||||
func (xmlProc *XMLRecordsProcessor) recordToCDR(xmlEntity tree.Node, cdrcCfg *config.CdrcConfig) (*engine.CDR, error) {
|
||||
func (xmlProc *XMLRecordsProcessor) recordToCDR(xmlEntity *etree.Element, cdrcCfg *config.CdrcConfig) (*engine.CDR, error) {
|
||||
cdr := &engine.CDR{OriginHost: "0.0.0.0", Source: cdrcCfg.CdrSourceId, ExtraFields: make(map[string]string), Cost: -1}
|
||||
var lazyHttpFields []*config.FCTemplate
|
||||
var err error
|
||||
@@ -236,14 +205,14 @@ func (xmlProc *XMLRecordsProcessor) recordToCDR(xmlEntity tree.Node, cdrcCfg *co
|
||||
}
|
||||
|
||||
// newXmlProvider constructs a DataProvider
|
||||
func newXmlProvider(req tree.Node, cdrPath utils.HierarchyPath) (dP config.DataProvider) {
|
||||
func newXmlProvider(req *etree.Element, cdrPath utils.HierarchyPath) (dP config.DataProvider) {
|
||||
dP = &xmlProvider{req: req, cdrPath: cdrPath, cache: config.NewNavigableMap(nil)}
|
||||
return
|
||||
}
|
||||
|
||||
// xmlProvider implements engine.DataProvider so we can pass it to filters
|
||||
type xmlProvider struct {
|
||||
req tree.Node
|
||||
req *etree.Element
|
||||
cdrPath utils.HierarchyPath //used to compute relative path
|
||||
cache *config.NavigableMap
|
||||
}
|
||||
@@ -265,8 +234,8 @@ func (xP *xmlProvider) FieldAsInterface(fldPath []string) (data interface{}, err
|
||||
}
|
||||
err = nil // cancel previous err
|
||||
absolutePath := utils.ParseHierarchyPath(fldPath[0], "")
|
||||
relPath := utils.HierarchyPath(absolutePath[len(xP.cdrPath)-1:]) // Need relative path to the xmlElmnt
|
||||
data, err = elementText(xP.req, relPath.AsString("/", true))
|
||||
relPath := utils.HierarchyPath(absolutePath[len(xP.cdrPath):]) // Need relative path to the xmlElmnt
|
||||
data, err = elementText(xP.req, relPath.AsString("/", false))
|
||||
xP.cache.Set(fldPath, data, false)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestXmlIT2InitCdrDb(t *testing.T) {
|
||||
|
||||
func TestXmlIT2CreateCdrDirs(t *testing.T) {
|
||||
for _, cdrcProfiles := range xmlCfg.CdrcProfiles {
|
||||
for i, cdrcInst := range cdrcProfiles {
|
||||
for _, cdrcInst := range cdrcProfiles {
|
||||
for _, dir := range []string{cdrcInst.CdrInDir, cdrcInst.CdrOutDir} {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Fatal("Error removing folder: ", dir, err)
|
||||
@@ -158,7 +158,7 @@ func TestXmlIT2CreateCdrDirs(t *testing.T) {
|
||||
t.Fatal("Error creating folder: ", dir, err)
|
||||
}
|
||||
}
|
||||
if i == 0 { // Initialize the folders to check later
|
||||
if cdrcInst.ID == "XMLWithFilter" { // Initialize the folders to check later
|
||||
xmlPathIn1 = cdrcInst.CdrInDir
|
||||
xmlPathOut1 = cdrcInst.CdrOutDir
|
||||
}
|
||||
@@ -216,3 +216,89 @@ func TestXmlIT2KillEngine(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Begin tests for cdrc xml with new filters
|
||||
func TestXmlIT3InitConfig(t *testing.T) {
|
||||
var err error
|
||||
xmlCfgPath = path.Join(*dataDir, "conf", "samples", "cdrcxmlwithfilter")
|
||||
if xmlCfg, err = config.NewCGRConfigFromFolder(xmlCfgPath); err != nil {
|
||||
t.Fatal("Got config error: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// InitDb so we can rely on count
|
||||
func TestXmlIT3InitCdrDb(t *testing.T) {
|
||||
if err := engine.InitStorDb(xmlCfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXmlIT3CreateCdrDirs(t *testing.T) {
|
||||
for _, cdrcProfiles := range xmlCfg.CdrcProfiles {
|
||||
for _, cdrcInst := range cdrcProfiles {
|
||||
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)
|
||||
}
|
||||
}
|
||||
if cdrcInst.ID == "msw_xml" { // Initialize the folders to check later
|
||||
xmlPathIn1 = cdrcInst.CdrInDir
|
||||
xmlPathOut1 = cdrcInst.CdrOutDir
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestXmlIT3StartEngine(t *testing.T) {
|
||||
if _, err := engine.StopStartEngine(xmlCfgPath, *waitRater); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect rpc client to rater
|
||||
func TestXmlIT3RpcConn(t *testing.T) {
|
||||
var err error
|
||||
cdrcXmlRPC, err = jsonrpc.Dial("tcp", xmlCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
|
||||
if err != nil {
|
||||
t.Fatal("Could not connect to rater: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// The default scenario, out of cdrc defined in .cfg file
|
||||
func TestXmlIT3HandleCdr1File(t *testing.T) {
|
||||
fileName := "file1.xml"
|
||||
tmpFilePath := path.Join("/tmp", fileName)
|
||||
if err := ioutil.WriteFile(tmpFilePath, []byte(xmlContent), 0644); err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if err := os.Rename(tmpFilePath, path.Join(xmlPathIn1, fileName)); err != nil {
|
||||
t.Fatal("Error moving file to processing directory: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXmlIT3ProcessedFiles(t *testing.T) {
|
||||
time.Sleep(time.Duration(2**waitRater) * time.Millisecond)
|
||||
if outContent1, err := ioutil.ReadFile(path.Join(xmlPathOut1, "file1.xml")); err != nil {
|
||||
t.Error(err)
|
||||
} else if xmlContent != string(outContent1) {
|
||||
t.Errorf("Expecting: %q, received: %q", xmlContent, string(outContent1))
|
||||
}
|
||||
}
|
||||
|
||||
func TestXmlIT3AnalyseCDRs(t *testing.T) {
|
||||
var reply []*engine.ExternalCDR
|
||||
if err := cdrcXmlRPC.Call("ApierV2.GetCdrs", utils.RPCCDRsFilter{}, &reply); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if len(reply) != 3 {
|
||||
t.Error("Unexpected number of CDRs returned: ", len(reply))
|
||||
}
|
||||
}
|
||||
|
||||
func TestXmlIT3KillEngine(t *testing.T) {
|
||||
if err := engine.KillEngine(*waitRater); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
301
cdrc/xml_test.go
301
cdrc/xml_test.go
@@ -19,13 +19,13 @@ package cdrc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"path"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ChrisTrenkamp/goxpath"
|
||||
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
|
||||
"github.com/beevik/etree"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
@@ -166,28 +166,27 @@ var cdrXmlBroadsoft = `<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
</cdrData>
|
||||
</broadWorksCDR>`
|
||||
|
||||
func optsNotStrict(s *xmltree.ParseOptions) {
|
||||
s.Strict = false
|
||||
}
|
||||
|
||||
func TestXMLElementText(t *testing.T) {
|
||||
xp := goxpath.MustParse(path.Join("/broadWorksCDR/cdrData/"))
|
||||
xmlTree := xmltree.MustParseXML(bytes.NewBufferString(cdrXmlBroadsoft), optsNotStrict)
|
||||
cdrs, err := xp.ExecNode(xmlTree)
|
||||
if err != nil {
|
||||
doc := etree.NewDocument()
|
||||
doc.ReadSettings.CharsetReader = func(label string, input io.Reader) (io.Reader, error) {
|
||||
return input, nil
|
||||
}
|
||||
if err := doc.ReadFromBytes([]byte(cdrXmlBroadsoft)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cdrs := doc.FindElements(path.Join("/broadWorksCDR/cdrData/"))
|
||||
cdrWithoutUserNr := cdrs[0]
|
||||
if _, err := elementText(cdrWithoutUserNr, "cdrData/basicModule/userNumber"); err != utils.ErrNotFound {
|
||||
if _, err := elementText(cdrWithoutUserNr, "basicModule/userNumber"); err != utils.ErrNotFound {
|
||||
t.Error(err)
|
||||
}
|
||||
cdrWithUser := cdrs[1]
|
||||
if val, err := elementText(cdrWithUser, "cdrData/basicModule/userNumber"); err != nil {
|
||||
if val, err := elementText(cdrWithUser, "basicModule/userNumber"); err != nil {
|
||||
t.Error(err)
|
||||
} else if val != "1001" {
|
||||
t.Errorf("Expecting: 1001, received: %s", val)
|
||||
}
|
||||
if val, err := elementText(cdrWithUser, "/cdrData/centrexModule/locationList/locationInformation/locationType"); err != nil {
|
||||
if val, err := elementText(cdrWithUser, "centrexModule/locationList/locationInformation/locationType"); err != nil {
|
||||
t.Error(err)
|
||||
} else if val != "Primary Device" {
|
||||
t.Errorf("Expecting: <Primary Device>, received: <%s>", val)
|
||||
@@ -195,12 +194,15 @@ func TestXMLElementText(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestXMLHandlerSubstractUsage(t *testing.T) {
|
||||
xp := goxpath.MustParse(path.Join("/broadWorksCDR/cdrData/"))
|
||||
xmlTree := xmltree.MustParseXML(bytes.NewBufferString(cdrXmlBroadsoft), optsNotStrict)
|
||||
cdrs, err := xp.ExecNode(xmlTree)
|
||||
if err != nil {
|
||||
doc := etree.NewDocument()
|
||||
doc.ReadSettings.CharsetReader = func(label string, input io.Reader) (io.Reader, error) {
|
||||
return input, nil
|
||||
}
|
||||
if err := doc.ReadFromBytes([]byte(cdrXmlBroadsoft)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cdrs := doc.FindElements(path.Join("/broadWorksCDR/cdrData/"))
|
||||
cdrWithUsage := cdrs[1]
|
||||
if usage, err := handlerSubstractUsage(cdrWithUsage,
|
||||
config.NewRSRParsersMustCompile("~broadWorksCDR>cdrData>basicModule>releaseTime;|;~broadWorksCDR>cdrData>basicModule>answerTime", true),
|
||||
@@ -352,3 +354,268 @@ func TestXMLRPProcessWithNewFilters(t *testing.T) {
|
||||
t.Errorf("Expecting: %+v\n, received: %+v\n", expectedCDRs, cdrs)
|
||||
}
|
||||
}
|
||||
|
||||
var xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<File xmlns="http://www.metaswitch.com/cfs/billing/V1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" compatibility="9">
|
||||
<FileHeader seqnum="169">
|
||||
<EquipmentType>Metaswitch CFS</EquipmentType>
|
||||
<EquipmentId></EquipmentId>
|
||||
<CreateTime>1510225200002</CreateTime>
|
||||
</FileHeader>
|
||||
<CDRs>
|
||||
<Call seqnum="0000000001" error="no" longcall="false" testcall="false" class="0" operator="false" correlator="397828983391" connected="false">
|
||||
<CallType>National</CallType>
|
||||
<Features/>
|
||||
<ReleasingParty>Orig</ReleasingParty>
|
||||
<ReleaseReason type="q850" loc="u">19</ReleaseReason>
|
||||
<ReleaseReason type="sip">480</ReleaseReason>
|
||||
<ReleaseReason type="internal">No answer</ReleaseReason>
|
||||
<InternalIndex>223007622</InternalIndex>
|
||||
<OrigParty xsi:type="BusinessLinePartyType" subscribergroup="Subscribers in Guernsey, NJ" billingtype="flat rate" privacy="false" cpc="normal" ani-ii="00">
|
||||
<SubscriberAddr type="e164">+27110493421</SubscriberAddr>
|
||||
<CallingPartyAddr type="e164">+27110493421</CallingPartyAddr>
|
||||
<ChargeAddr type="e164">+27110493421</ChargeAddr>
|
||||
<BusinessGroupName>Ro_test</BusinessGroupName>
|
||||
<SIPCallId>0gQAAC8WAAACBAAALxYAAD57SAEV7ekv/OSKkO7qmD82OmbfHO+Z7wIZJkXdCv8z@10.170.248.200</SIPCallId>
|
||||
</OrigParty>
|
||||
<TermParty xsi:type="NetworkTrunkPartyType">
|
||||
<TrunkGroup type="sip" trunkname="IMS Core">
|
||||
<TrunkGroupId>1</TrunkGroupId>
|
||||
<TrunkMemberId>0</TrunkMemberId>
|
||||
</TrunkGroup>
|
||||
<SIPCallId>8824071D@10.170.248.140</SIPCallId>
|
||||
<Reason type="q850">19</Reason>
|
||||
<Reason type="sip">480</Reason>
|
||||
</TermParty>
|
||||
<RoutingInfo>
|
||||
<RequestedAddr type="unknown">0763371551</RequestedAddr>
|
||||
<DestAddr type="e164">+270763371551</DestAddr>
|
||||
<RoutedAddr type="national">0763371551</RoutedAddr>
|
||||
<CallingPartyRoutedAddr type="national">110493421</CallingPartyRoutedAddr>
|
||||
<CallingPartyOrigAddr type="national">110493421</CallingPartyOrigAddr>
|
||||
</RoutingInfo>
|
||||
<CarrierSelectInfo>
|
||||
<CarrierOperatorInvolved>False</CarrierOperatorInvolved>
|
||||
<SelectionMethod>NetworkDefault</SelectionMethod>
|
||||
<NetworkId>0</NetworkId>
|
||||
</CarrierSelectInfo>
|
||||
<SignalingInfo>
|
||||
<MediaCapabilityRequested>Speech</MediaCapabilityRequested>
|
||||
<PChargingVector>
|
||||
<icidvalue>13442698e525ad2c21251f76479ab2b4</icidvalue>
|
||||
<origioi>voice.lab.liquidtelecom.net</origioi>
|
||||
</PChargingVector>
|
||||
</SignalingInfo>
|
||||
<IcSeizeTime>1510225513055</IcSeizeTime>
|
||||
<OgSeizeTime>1510225513304</OgSeizeTime>
|
||||
<RingingTime>20160419210005.247</RingingTime>
|
||||
<ConnectTime>20160419210006.813</ConnectTime>
|
||||
<DisconnectTime>1510225516981</DisconnectTime>
|
||||
<ReleaseTime>20160419210020.296</ReleaseTime>
|
||||
<CompleteTime>1510225516981</CompleteTime>
|
||||
</Call>
|
||||
<Call seqnum="0000000002" error="no" longcall="false" testcall="false" class="0" operator="false" correlator="402123969565" connected="true">
|
||||
<CallType>Premium</CallType>
|
||||
<Features/>
|
||||
<ReleasingParty>Orig</ReleasingParty>
|
||||
<ReleaseReason type="q850" loc="u">16</ReleaseReason>
|
||||
<ReleaseReason type="internal">No error</ReleaseReason>
|
||||
<InternalIndex>223007623</InternalIndex>
|
||||
<OrigParty xsi:type="BusinessLinePartyType" subscribergroup="Subscribers in Guernsey, NJ" billingtype="flat rate" privacy="false" cpc="normal" ani-ii="00">
|
||||
<SubscriberAddr type="e164">+27110493421</SubscriberAddr>
|
||||
<CallingPartyAddr type="e164">+27110493421</CallingPartyAddr>
|
||||
<ChargeAddr type="e164">+27110493421</ChargeAddr>
|
||||
<BusinessGroupName>Ro_test</BusinessGroupName>
|
||||
<SIPCallId>0gQAAC8WAAACBAAALxYAAPkyWDO29Do1SyxNi5UV71mJYEIEkfNa9wCFCCjY2asU@10.170.248.200</SIPCallId>
|
||||
</OrigParty>
|
||||
<TermParty xsi:type="NetworkTrunkPartyType">
|
||||
<TrunkGroup type="sip" trunkname="IMS Core">
|
||||
<TrunkGroupId>1</TrunkGroupId>
|
||||
<TrunkMemberId>0</TrunkMemberId>
|
||||
</TrunkGroup>
|
||||
<SIPCallId>8E450FA1@10.170.248.140</SIPCallId>
|
||||
</TermParty>
|
||||
<RoutingInfo>
|
||||
<RequestedAddr type="unknown">0843073451</RequestedAddr>
|
||||
<DestAddr type="e164">+270843073451</DestAddr>
|
||||
<RoutedAddr type="national">0843073451</RoutedAddr>
|
||||
<CallingPartyRoutedAddr type="national">110493421</CallingPartyRoutedAddr>
|
||||
<CallingPartyOrigAddr type="national">110493421</CallingPartyOrigAddr>
|
||||
</RoutingInfo>
|
||||
<CarrierSelectInfo>
|
||||
<CarrierOperatorInvolved>False</CarrierOperatorInvolved>
|
||||
<SelectionMethod>NetworkDefault</SelectionMethod>
|
||||
<NetworkId>0</NetworkId>
|
||||
</CarrierSelectInfo>
|
||||
<SignalingInfo>
|
||||
<MediaCapabilityRequested>Speech</MediaCapabilityRequested>
|
||||
<PChargingVector>
|
||||
<icidvalue>46d7974398c2671016afccc3f2c428c7</icidvalue>
|
||||
<origioi>voice.lab.liquidtelecom.net</origioi>
|
||||
</PChargingVector>
|
||||
</SignalingInfo>
|
||||
<IcSeizeTime>1510225531933</IcSeizeTime>
|
||||
<OgSeizeTime>1510225532183</OgSeizeTime>
|
||||
<RingingTime>20160419210005.247</RingingTime>
|
||||
<ConnectTime>20160419210006.813</ConnectTime>
|
||||
<DisconnectTime>1510225593101</DisconnectTime>
|
||||
<ReleaseTime>20160419210020.296</ReleaseTime>
|
||||
<CompleteTime>1510225593101</CompleteTime>
|
||||
</Call>
|
||||
<Call seqnum="0000000003" error="no" longcall="false" testcall="false" class="0" operator="false" correlator="406419270822" connected="true">
|
||||
<CallType>International</CallType>
|
||||
<Features/>
|
||||
<ReleasingParty>Orig</ReleasingParty>
|
||||
<ReleaseReason type="q850" loc="u">16</ReleaseReason>
|
||||
<ReleaseReason type="internal">No error</ReleaseReason>
|
||||
<InternalIndex>223007624</InternalIndex>
|
||||
<OrigParty xsi:type="BusinessLinePartyType" subscribergroup="Subscribers in Guernsey, NJ" billingtype="flat rate" privacy="false" cpc="normal" ani-ii="00">
|
||||
<SubscriberAddr type="e164">+27110493421</SubscriberAddr>
|
||||
<CallingPartyAddr type="e164">+27110493421</CallingPartyAddr>
|
||||
<ChargeAddr type="e164">+27110493421</ChargeAddr>
|
||||
<BusinessGroupName>Ro_test</BusinessGroupName>
|
||||
<SIPCallId>0gQAAC8WAAACBAAALxYAAJrUscTicyU5GtjPyQnpAeuNmz9p/bdOoR/Mk9RXciOI@10.170.248.200</SIPCallId>
|
||||
</OrigParty>
|
||||
<TermParty xsi:type="NetworkTrunkPartyType">
|
||||
<TrunkGroup type="sip" trunkname="IMS Core">
|
||||
<TrunkGroupId>1</TrunkGroupId>
|
||||
<TrunkMemberId>0</TrunkMemberId>
|
||||
</TrunkGroup>
|
||||
<SIPCallId>BC8B2801@10.170.248.140</SIPCallId>
|
||||
</TermParty>
|
||||
<RoutingInfo>
|
||||
<RequestedAddr type="unknown">263772822306</RequestedAddr>
|
||||
<DestAddr type="e164">+263772822306</DestAddr>
|
||||
<RoutedAddr type="e164">263772822306</RoutedAddr>
|
||||
<CallingPartyRoutedAddr type="national">110493421</CallingPartyRoutedAddr>
|
||||
<CallingPartyOrigAddr type="national">110493421</CallingPartyOrigAddr>
|
||||
</RoutingInfo>
|
||||
<CarrierSelectInfo>
|
||||
<CarrierOperatorInvolved>False</CarrierOperatorInvolved>
|
||||
<SelectionMethod>NetworkDefault</SelectionMethod>
|
||||
<NetworkId>0</NetworkId>
|
||||
</CarrierSelectInfo>
|
||||
<SignalingInfo>
|
||||
<MediaCapabilityRequested>Speech</MediaCapabilityRequested>
|
||||
<PChargingVector>
|
||||
<icidvalue>750b8b73e41ba7b59b21240758522268</icidvalue>
|
||||
<origioi>voice.lab.liquidtelecom.net</origioi>
|
||||
</PChargingVector>
|
||||
</SignalingInfo>
|
||||
<IcSeizeTime>1510225865894</IcSeizeTime>
|
||||
<OgSeizeTime>1510225866144</OgSeizeTime>
|
||||
<RingingTime>20160419210005.247</RingingTime>
|
||||
<ConnectTime>20160419210006.813</ConnectTime>
|
||||
<DisconnectTime>1510225916144</DisconnectTime>
|
||||
<ReleaseTime>20160419210020.296</ReleaseTime>
|
||||
<CompleteTime>1510225916144</CompleteTime>
|
||||
</Call>
|
||||
</CDRs>
|
||||
<FileFooter>
|
||||
<LastModTime>1510227591467</LastModTime>
|
||||
<NumCDRs>3</NumCDRs>
|
||||
<DataErroredCDRs>0</DataErroredCDRs>
|
||||
<WriteErroredCDRs>0</WriteErroredCDRs>
|
||||
</FileFooter>
|
||||
</File>
|
||||
`
|
||||
|
||||
func TestXMLElementText2(t *testing.T) {
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromString(xmlContent); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
hPath2 := utils.ParseHierarchyPath("File>CDRs>Call", "")
|
||||
cdrs := doc.Root().FindElements(hPath2.AsString("/", true))
|
||||
if len(cdrs) != 3 {
|
||||
t.Errorf("Expecting: 3, received: %+v", len(cdrs))
|
||||
}
|
||||
|
||||
if _, err := elementText(cdrs[0], "SignalingInfo/PChargingVector/test"); err != utils.ErrNotFound {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
absolutePath := utils.ParseHierarchyPath("File>CDRs>Call>SignalingInfo>PChargingVector>icidvalue", "")
|
||||
hPath := utils.ParseHierarchyPath(cdrs[1].GetPath(), "")
|
||||
relPath := utils.HierarchyPath(absolutePath[len(hPath):]) // Need relative path to the xmlElmnt
|
||||
|
||||
if val, err := elementText(cdrs[1], relPath.AsString("/", false)); err != nil {
|
||||
t.Error(err)
|
||||
} else if val != "46d7974398c2671016afccc3f2c428c7" {
|
||||
t.Errorf("Expecting: 46d7974398c2671016afccc3f2c428c7, received: %s", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXMLRPProcessWithNewFilters2(t *testing.T) {
|
||||
cdrcCfgs := []*config.CdrcConfig{
|
||||
&config.CdrcConfig{
|
||||
ID: "msw_xml",
|
||||
Enabled: true,
|
||||
CdrFormat: "xml",
|
||||
DataUsageMultiplyFactor: 1024,
|
||||
CDRPath: utils.HierarchyPath([]string{"File", "CDRs", "Call"}),
|
||||
CdrSourceId: "zw_cfs1",
|
||||
Filters: []string{},
|
||||
ContentFields: []*config.FCTemplate{
|
||||
&config.FCTemplate{ID: "TOR", Type: utils.META_COMPOSED, FieldId: utils.ToR,
|
||||
Value: config.NewRSRParsersMustCompile("*voice", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "OriginID", Type: utils.META_COMPOSED, FieldId: utils.OriginID,
|
||||
Value: config.NewRSRParsersMustCompile("~File>CDRs>Call>SignalingInfo>PChargingVector>icidvalue", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "RequestType", Type: utils.META_COMPOSED, FieldId: utils.RequestType,
|
||||
Value: config.NewRSRParsersMustCompile("*rated", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "Tenant", Type: utils.META_COMPOSED, FieldId: utils.Tenant,
|
||||
Value: config.NewRSRParsersMustCompile("XX.liquid.tel", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "Category", Type: utils.META_COMPOSED, FieldId: utils.Category,
|
||||
Value: config.NewRSRParsersMustCompile("call", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "Account", Type: utils.META_COMPOSED, FieldId: utils.Account,
|
||||
Value: config.NewRSRParsersMustCompile("~File>CDRs>Call>OrigParty>SubscriberAddr", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "Destination", Type: utils.META_COMPOSED, FieldId: utils.Destination,
|
||||
Value: config.NewRSRParsersMustCompile("~File>CDRs>Call>RoutingInfo>DestAddr", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "SetupTime", Type: utils.META_COMPOSED, FieldId: utils.SetupTime,
|
||||
Value: config.NewRSRParsersMustCompile("~File>CDRs>Call>RingingTime", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "AnswerTime", Type: utils.META_COMPOSED, FieldId: utils.AnswerTime,
|
||||
Value: config.NewRSRParsersMustCompile("~File>CDRs>Call>ConnectTime", true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "Usage", Type: utils.META_HANDLER,
|
||||
FieldId: utils.Usage, HandlerId: utils.HandlerSubstractUsage,
|
||||
Value: config.NewRSRParsersMustCompile("~File>CDRs>Call>ReleaseTime;|;~File>CDRs>Call>ConnectTime",
|
||||
true), Mandatory: true},
|
||||
&config.FCTemplate{ID: "UsageSeconds", Type: utils.META_COMPOSED, FieldId: utils.Usage,
|
||||
Value: config.NewRSRParsersMustCompile("s", true), Mandatory: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
data, _ := engine.NewMapStorage()
|
||||
defaultCfg, err := config.NewDefaultCGRConfig()
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
xmlRP, err := NewXMLRecordsProcessor(bytes.NewBufferString(xmlContent),
|
||||
utils.HierarchyPath([]string{"File", "CDRs", "Call"}), "UTC", true,
|
||||
cdrcCfgs, engine.NewFilterS(defaultCfg, nil, engine.NewDataManager(data)))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var cdrs []*engine.CDR
|
||||
for i := 0; i < 4; i++ {
|
||||
cdrs, err = xmlRP.ProcessNextRecord()
|
||||
if i == 1 { // Take second CDR since the first one cannot be processed
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
expectedCDRs := []*engine.CDR{
|
||||
&engine.CDR{CGRID: "7c6170203bd3d5b34e9fce366b5ee7d621217824",
|
||||
OriginHost: "0.0.0.0", Source: "zw_cfs1", OriginID: "46d7974398c2671016afccc3f2c428c7",
|
||||
ToR: "*voice", RequestType: "*rated", Tenant: "XX.liquid.tel",
|
||||
Category: "call", Account: "+27110493421", Destination: "+270843073451",
|
||||
SetupTime: time.Date(2016, 4, 19, 21, 0, 5, 247000000, time.UTC),
|
||||
AnswerTime: time.Date(2016, 4, 19, 21, 0, 6, 813000000, time.UTC),
|
||||
Usage: time.Duration(13483000000),
|
||||
ExtraFields: map[string]string{}, Cost: -1},
|
||||
}
|
||||
if !reflect.DeepEqual(expectedCDRs, cdrs) {
|
||||
t.Errorf("Expecting: %+v\n, received: %+v\n", expectedCDRs, cdrs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,29 @@
|
||||
{"id": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*substract_usage", "value": "~broadWorksCDR>cdrData>basicModule>releaseTime;|;~broadWorksCDR>cdrData>basicModule>answerTime", "mandatory": true},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "msw_xml", // identifier of the CDRC runner
|
||||
"enabled": true, // enable CDR client functionality
|
||||
"cdr_format": "xml", // CDR file format <csv|freeswitch_csv|fwv|opensips_flatstore|partial_csv>
|
||||
"cdr_in_dir": "/tmp/cdrcxmlwithfilters2/xmlit2/in",
|
||||
"cdr_out_dir": "/tmp/cdrcxmlwithfilters2/xmlit2/out",
|
||||
"cdr_path": "File>CDRs>Call", // path towards one CDR element in case of XML CDRs
|
||||
"cdr_source_id": "zw_cfs1", // free form field, tag identifying the source of the CDRs within CDRS database
|
||||
"content_fields":[ // import content_fields template, id will match internally CDR field, in case of .csv value will be represented by index of the field value
|
||||
{"id": "TOR", "field_id": "ToR", "type": "*composed", "value": "*voice", "mandatory": true},
|
||||
{"id": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "~File>CDRs>Call>SignalingInfo>PChargingVector>icidvalue", "mandatory": true},
|
||||
{"id": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "*rated", "mandatory": true},
|
||||
{"id": "Direction", "field_id": "Direction", "type": "*composed", "value": "*out", "mandatory": true},
|
||||
{"id": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "XX.liquid.tel", "mandatory": true},
|
||||
{"id": "Category", "field_id": "Category", "type": "*composed", "value": "call", "mandatory": true},
|
||||
{"id": "Account", "field_id": "Account", "type": "*composed", "value": "~File>CDRs>Call>OrigParty>SubscriberAddr", "mandatory": true},
|
||||
{"id": "Subject", "field_id": "Subject", "type": "*composed", "value": "~File>CDRs>Call>OrigParty>SubscriberAddr", "mandatory": true},
|
||||
{"id": "Destination", "field_id": "Destination", "type": "*composed", "value": "~File>CDRs>Call>RoutingInfo>DestAddr", "mandatory": true},
|
||||
{"id": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "~File>CDRs>Call>RingingTime", "mandatory": true},
|
||||
{"id": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "~File>CDRs>Call>ConnectTime", "mandatory": true},
|
||||
{"id": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*substract_usage", "value": "~File>CDRs>Call>ReleaseTime;|;~File>CDRs>Call>ConnectTime", "mandatory": true}
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
|
||||
2
glide.lock
generated
2
glide.lock
generated
@@ -125,4 +125,6 @@ imports:
|
||||
- internal/scram
|
||||
- name: github.com/dlintw/goconf
|
||||
version: dcc070983490608a14480e3bf943bad464785df5
|
||||
- name: github.com/beevik/etree
|
||||
version: 90dafc1e1f114dfb9576218bb43c03fb854d1b34
|
||||
testImports: []
|
||||
|
||||
@@ -41,4 +41,5 @@ import:
|
||||
- package: github.com/streadway/amqp
|
||||
- package: github.com/cgrates/radigo
|
||||
- package: github.com/cgrates/ltcache
|
||||
- package: github.com/dlintw/goconf
|
||||
- package: github.com/dlintw/goconf
|
||||
- package: github.com/beevik/etree
|
||||
Reference in New Issue
Block a user