mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Add sanity check to prevent xml reader panic
HierarchyPath parser now returns nil when the path is empty (instead of a string slice with one EmptyString element). If the prefix is set to true, when calling the AsString method on a nil HierarchyPath, only the separator will be returned. This avoids a nil expr error coming from the xmlquery library. Use the Query and QueryAll functions from the xmlquery package to be able to handle the errors ourselves and avoid panics. Added an integration tes for the special case where the xml_root_path field is left empty. Before the change it used to trim the root element from the path slice when attempting to retrieve a the relative path slice.
This commit is contained in:
committed by
Dan Christian Bogos
parent
f36ea1676f
commit
dd82bb3c4b
@@ -162,7 +162,11 @@ func (hU *httpXmlDP) FieldAsInterface(fldPath []string) (data any, err error) {
|
||||
}
|
||||
//convert workPath to HierarchyPath
|
||||
hrPath := utils.HierarchyPath(workPath)
|
||||
elmnt := xmlquery.FindOne(hU.xmlDoc, hrPath.AsString("/", false))
|
||||
var elmnt *xmlquery.Node
|
||||
elmnt, err = xmlquery.Query(hU.xmlDoc, hrPath.AsString("/", false))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elmnt == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -466,7 +466,6 @@ func testCGRConfigReloadERs(t *testing.T) {
|
||||
Flags: flagsDefault,
|
||||
Fields: content,
|
||||
CacheDumpFields: []*FCTemplate{},
|
||||
XmlRootPath: utils.HierarchyPath{utils.EmptyString},
|
||||
},
|
||||
{
|
||||
ID: "file_reader1",
|
||||
@@ -479,7 +478,6 @@ func testCGRConfigReloadERs(t *testing.T) {
|
||||
Flags: flags,
|
||||
Fields: content,
|
||||
CacheDumpFields: []*FCTemplate{},
|
||||
XmlRootPath: utils.HierarchyPath{utils.EmptyString},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -858,7 +856,7 @@ func testCgrCfgV1ReloadConfigSection(t *testing.T) {
|
||||
"Tenant": nil,
|
||||
"Timezone": "",
|
||||
"Type": "*none",
|
||||
"XmlRootPath": []any{utils.EmptyString},
|
||||
"XmlRootPath": nil,
|
||||
},
|
||||
map[string]any{
|
||||
"CacheDumpFields": []any{},
|
||||
@@ -879,7 +877,7 @@ func testCgrCfgV1ReloadConfigSection(t *testing.T) {
|
||||
"Tenant": nil,
|
||||
"Timezone": "",
|
||||
"Type": "*file_csv",
|
||||
"XmlRootPath": []any{utils.EmptyString},
|
||||
"XmlRootPath": nil,
|
||||
"Fields": content,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1691,7 +1691,7 @@ func TestCgrCdfEventReader(t *testing.T) {
|
||||
ConcurrentReqs: 1024,
|
||||
SourcePath: "/var/spool/cgrates/ers/in",
|
||||
ProcessedPath: "/var/spool/cgrates/ers/out",
|
||||
XmlRootPath: utils.HierarchyPath{utils.EmptyString},
|
||||
XmlRootPath: nil,
|
||||
Tenant: nil,
|
||||
Timezone: utils.EmptyString,
|
||||
Filters: []string{},
|
||||
@@ -1743,7 +1743,7 @@ func TestCgrCfgEventReaderDefault(t *testing.T) {
|
||||
ConcurrentReqs: 1024,
|
||||
SourcePath: "/var/spool/cgrates/ers/in",
|
||||
ProcessedPath: "/var/spool/cgrates/ers/out",
|
||||
XmlRootPath: utils.HierarchyPath{utils.EmptyString},
|
||||
XmlRootPath: nil,
|
||||
Tenant: nil,
|
||||
Timezone: utils.EmptyString,
|
||||
Filters: nil,
|
||||
|
||||
@@ -20,6 +20,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -512,6 +513,26 @@ func (cfg *CGRConfig) checkConfigSanity() error {
|
||||
if field.Type != utils.META_NONE && field.Path == utils.EmptyString {
|
||||
return fmt.Errorf("<%s> %s for %s at %s", utils.ERs, utils.NewErrMandatoryIeMissing(utils.Path), rdr.ID, field.Tag)
|
||||
}
|
||||
|
||||
// The following sanity check prevents a "slice bounds out of range" panic.
|
||||
if rdr.Type == utils.MetaFileXML && !utils.IsSliceMember([]string{utils.META_NONE, utils.META_CONSTANT,
|
||||
utils.META_FILLER, utils.MetaRemoteHost}, field.Type) && len(field.Value) != 0 {
|
||||
|
||||
// Retrieve the number of elements of the parser rule with the fewest elements.
|
||||
minRuleLength := math.MaxInt
|
||||
for _, rule := range field.Value {
|
||||
if ruleLen := len(strings.Split(rule.AttrName(), utils.NestingSep)); minRuleLength > ruleLen {
|
||||
minRuleLength = ruleLen
|
||||
}
|
||||
}
|
||||
|
||||
if len(rdr.XmlRootPath) >= minRuleLength {
|
||||
return fmt.Errorf("<%s> %s for reader %s at %s",
|
||||
utils.ERs,
|
||||
"len of xml_root_path elements cannot be equal to or exceed the number of value rule elements",
|
||||
rdr.ID, field.Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ func TestEventReaderLoadFromJSON(t *testing.T) {
|
||||
ConcurrentReqs: 1024,
|
||||
SourcePath: "/var/spool/cgrates/ers/in",
|
||||
ProcessedPath: "/var/spool/cgrates/ers/out",
|
||||
XmlRootPath: utils.HierarchyPath{utils.EmptyString},
|
||||
XmlRootPath: nil,
|
||||
Tenant: nil,
|
||||
Timezone: utils.EmptyString,
|
||||
Filters: []string{},
|
||||
@@ -152,7 +152,7 @@ func TestEventReaderLoadFromJSON(t *testing.T) {
|
||||
ConcurrentReqs: 1024,
|
||||
SourcePath: "/tmp/ers/in",
|
||||
ProcessedPath: "/tmp/ers/out",
|
||||
XmlRootPath: utils.HierarchyPath{utils.EmptyString},
|
||||
XmlRootPath: nil,
|
||||
Tenant: nil,
|
||||
Timezone: utils.EmptyString,
|
||||
Filters: nil,
|
||||
@@ -270,7 +270,7 @@ func TestERsCfgAsMapInterface(t *testing.T) {
|
||||
"source_path": "/var/spool/cgrates/ers/in",
|
||||
"tenant": "",
|
||||
"timezone": "",
|
||||
"xml_root_path": []string{""},
|
||||
"xml_root_path": []string{},
|
||||
"cache_dump_fields": []map[string]any{},
|
||||
"concurrent_requests": 1024,
|
||||
"type": "*none",
|
||||
@@ -320,7 +320,7 @@ func TestERsCfgAsMapInterface(t *testing.T) {
|
||||
"source_path": "/tmp/ers/in",
|
||||
"tenant": "",
|
||||
"timezone": "",
|
||||
"xml_root_path": []string{""},
|
||||
"xml_root_path": []string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ type XmlProvider struct {
|
||||
// String is part of engine.utils.DataProvider interface
|
||||
// when called, it will display the already parsed values out of cache
|
||||
func (xP *XmlProvider) String() string {
|
||||
|
||||
// TODO: Find a proper way to display xP as string. Right now it only has
|
||||
// unexported fields, this will return an empty string.
|
||||
return utils.ToJSON(xP)
|
||||
}
|
||||
|
||||
@@ -62,11 +65,11 @@ func (xP *XmlProvider) FieldAsInterface(fldPath []string) (data any, err error)
|
||||
for i := range relPath {
|
||||
if sIdx := strings.Index(relPath[i], "["); sIdx != -1 {
|
||||
slctrStr = relPath[i][sIdx:]
|
||||
if slctrStr[len(slctrStr)-1:] != "]" {
|
||||
if slctrStr[len(slctrStr)-1] != ']' {
|
||||
return nil, fmt.Errorf("filter rule <%s> needs to end in ]", slctrStr)
|
||||
}
|
||||
relPath[i] = relPath[i][:sIdx]
|
||||
if slctrStr[1:2] != "@" {
|
||||
if slctrStr[1] != '@' {
|
||||
i, err := strconv.Atoi(slctrStr[1 : len(slctrStr)-1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -100,7 +103,10 @@ func (xP *XmlProvider) RemoteHost() net.Addr {
|
||||
// returns utils.ErrNotFound if the element is not found in the node
|
||||
// Make the method exportable until we remove the ers
|
||||
func ElementText(xmlElement *xmlquery.Node, elmntPath string) (string, error) {
|
||||
elmnt := xmlquery.FindOne(xmlElement, elmntPath)
|
||||
elmnt, err := xmlquery.Query(xmlElement, elmntPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if elmnt == nil {
|
||||
return "", utils.ErrNotFound
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
{"tag": "HDRExtra3", "path": "*cgreq.HDRExtra3", "type": "*variable", "value": "~*req.6", "mandatory": true},
|
||||
{"tag": "HDRExtra2", "path": "*cgreq.HDRExtra2", "type": "*variable", "value": "~*req.6", "mandatory": true},
|
||||
{"tag": "HDRExtra1", "path": "*cgreq.HDRExtra1", "type": "*variable", "value": "~*req.6", "mandatory": true},
|
||||
],
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "init_session",
|
||||
@@ -220,6 +220,29 @@
|
||||
{"tag": "Usage", "path": "*cgreq.Usage", "type": "*usage_difference", "value": "~*req.broadWorksCDR.cdrData.basicModule.releaseTime;~*req.broadWorksCDR.cdrData.basicModule.answerTime", "mandatory": true}
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "XML_EmptyRoot",
|
||||
"run_delay": "-1",
|
||||
"type": "*file_xml",
|
||||
"source_path": "/tmp/xmlErs2/in",
|
||||
"flags": ["*cdrs","*log"],
|
||||
"processed_path": "/tmp/xmlErs2/out",
|
||||
"xml_root_path": "",
|
||||
"fields":[
|
||||
{"tag": "ToR", "path": "*cgreq.ToR", "type": "*variable", "value": "~*req.root.partial_cdr.ToR", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.root.partial_cdr.TestCase", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "_", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.root.partial_cdr[1].ID", "mandatory": true},
|
||||
{"tag": "RequestType", "path": "*cgreq.RequestType", "type": "*variable", "value": "~*req.root.partial_cdr.RequestType", "mandatory": true},
|
||||
{"tag": "Tenant", "path": "*cgreq.Tenant", "type": "*variable", "value": "~*req.root.partial_cdr.Tenant", "mandatory": true},
|
||||
{"tag": "Category", "path": "*cgreq.Category", "type": "*constant", "value": "call", "mandatory": true},
|
||||
{"tag": "Account", "path": "*cgreq.Account", "type": "*variable", "value": "~*req.root.partial_cdr.Account", "mandatory": true},
|
||||
{"tag": "Destination", "path": "*cgreq.Destination", "type": "*variable", "value": "~*req.root.partial_cdr.Destination", "mandatory": true},
|
||||
{"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", "value": "~*req.root.partial_cdr.SetupTime", "mandatory": true},
|
||||
{"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", "value": "~*req.root.partial_cdr.AnswerTime", "mandatory": true},
|
||||
{"tag": "Usage", "path": "*cgreq.Usage", "type": "*usage_difference", "value": "~*req.root.partial_cdr.ReleaseTime;~*req.root.partial_cdr.AnswerTime", "mandatory": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "FWV1",
|
||||
"run_delay": "-1",
|
||||
|
||||
@@ -219,7 +219,30 @@
|
||||
{"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", "value": "~*req.broadWorksCDR.cdrData.basicModule.startTime", "mandatory": true},
|
||||
{"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", "value": "~*req.broadWorksCDR.cdrData.basicModule.answerTime", "mandatory": true},
|
||||
{"tag": "Usage", "path": "*cgreq.Usage", "type": "*usage_difference", "value": "~*req.broadWorksCDR.cdrData.basicModule.releaseTime;~*req.broadWorksCDR.cdrData.basicModule.answerTime", "mandatory": true}
|
||||
],
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "XML_EmptyRoot",
|
||||
"run_delay": "-1",
|
||||
"type": "*file_xml",
|
||||
"source_path": "/tmp/xmlErs2/in",
|
||||
"flags": ["*cdrs","*log"],
|
||||
"processed_path": "/tmp/xmlErs2/out",
|
||||
"xml_root_path": "",
|
||||
"fields":[
|
||||
{"tag": "ToR", "path": "*cgreq.ToR", "type": "*variable", "value": "~*req.root.partial_cdr.ToR", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.root.partial_cdr.TestCase", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "_", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.root.partial_cdr[1].ID", "mandatory": true},
|
||||
{"tag": "RequestType", "path": "*cgreq.RequestType", "type": "*variable", "value": "~*req.root.partial_cdr.RequestType", "mandatory": true},
|
||||
{"tag": "Tenant", "path": "*cgreq.Tenant", "type": "*variable", "value": "~*req.root.partial_cdr.Tenant", "mandatory": true},
|
||||
{"tag": "Category", "path": "*cgreq.Category", "type": "*constant", "value": "call", "mandatory": true},
|
||||
{"tag": "Account", "path": "*cgreq.Account", "type": "*variable", "value": "~*req.root.partial_cdr.Account", "mandatory": true},
|
||||
{"tag": "Destination", "path": "*cgreq.Destination", "type": "*variable", "value": "~*req.root.partial_cdr.Destination", "mandatory": true},
|
||||
{"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", "value": "~*req.root.partial_cdr.SetupTime", "mandatory": true},
|
||||
{"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", "value": "~*req.root.partial_cdr.AnswerTime", "mandatory": true},
|
||||
{"tag": "Usage", "path": "*cgreq.Usage", "type": "*usage_difference", "value": "~*req.root.partial_cdr.ReleaseTime;~*req.root.partial_cdr.AnswerTime", "mandatory": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "FWV1",
|
||||
|
||||
@@ -216,7 +216,30 @@
|
||||
{"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", "value": "~*req.broadWorksCDR.cdrData.basicModule.startTime", "mandatory": true},
|
||||
{"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", "value": "~*req.broadWorksCDR.cdrData.basicModule.answerTime", "mandatory": true},
|
||||
{"tag": "Usage", "path": "*cgreq.Usage", "type": "*usage_difference", "value": "~*req.broadWorksCDR.cdrData.basicModule.releaseTime;~*req.broadWorksCDR.cdrData.basicModule.answerTime", "mandatory": true}
|
||||
],
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "XML_EmptyRoot",
|
||||
"run_delay": "-1",
|
||||
"type": "*file_xml",
|
||||
"source_path": "/tmp/xmlErs2/in",
|
||||
"flags": ["*cdrs","*log"],
|
||||
"processed_path": "/tmp/xmlErs2/out",
|
||||
"xml_root_path": "",
|
||||
"fields":[
|
||||
{"tag": "ToR", "path": "*cgreq.ToR", "type": "*variable", "value": "~*req.root.partial_cdr.ToR", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.root.partial_cdr.TestCase", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "_", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.root.partial_cdr[1].ID", "mandatory": true},
|
||||
{"tag": "RequestType", "path": "*cgreq.RequestType", "type": "*variable", "value": "~*req.root.partial_cdr.RequestType", "mandatory": true},
|
||||
{"tag": "Tenant", "path": "*cgreq.Tenant", "type": "*variable", "value": "~*req.root.partial_cdr.Tenant", "mandatory": true},
|
||||
{"tag": "Category", "path": "*cgreq.Category", "type": "*constant", "value": "call", "mandatory": true},
|
||||
{"tag": "Account", "path": "*cgreq.Account", "type": "*variable", "value": "~*req.root.partial_cdr.Account", "mandatory": true},
|
||||
{"tag": "Destination", "path": "*cgreq.Destination", "type": "*variable", "value": "~*req.root.partial_cdr.Destination", "mandatory": true},
|
||||
{"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", "value": "~*req.root.partial_cdr.SetupTime", "mandatory": true},
|
||||
{"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", "value": "~*req.root.partial_cdr.AnswerTime", "mandatory": true},
|
||||
{"tag": "Usage", "path": "*cgreq.Usage", "type": "*usage_difference", "value": "~*req.root.partial_cdr.ReleaseTime;~*req.root.partial_cdr.AnswerTime", "mandatory": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "FWV1",
|
||||
|
||||
@@ -213,7 +213,30 @@
|
||||
{"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", "value": "~*req.broadWorksCDR.cdrData.basicModule.startTime", "mandatory": true},
|
||||
{"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", "value": "~*req.broadWorksCDR.cdrData.basicModule.answerTime", "mandatory": true},
|
||||
{"tag": "Usage", "path": "*cgreq.Usage", "type": "*usage_difference", "value": "~*req.broadWorksCDR.cdrData.basicModule.releaseTime;~*req.broadWorksCDR.cdrData.basicModule.answerTime", "mandatory": true}
|
||||
],
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "XML_EmptyRoot",
|
||||
"run_delay": "-1",
|
||||
"type": "*file_xml",
|
||||
"source_path": "/tmp/xmlErs2/in",
|
||||
"flags": ["*cdrs","*log"],
|
||||
"processed_path": "/tmp/xmlErs2/out",
|
||||
"xml_root_path": "",
|
||||
"fields":[
|
||||
{"tag": "ToR", "path": "*cgreq.ToR", "type": "*variable", "value": "~*req.root.partial_cdr.ToR", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.root.partial_cdr.TestCase", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "_", "mandatory": true},
|
||||
{"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.root.partial_cdr[1].ID", "mandatory": true},
|
||||
{"tag": "RequestType", "path": "*cgreq.RequestType", "type": "*variable", "value": "~*req.root.partial_cdr.RequestType", "mandatory": true},
|
||||
{"tag": "Tenant", "path": "*cgreq.Tenant", "type": "*variable", "value": "~*req.root.partial_cdr.Tenant", "mandatory": true},
|
||||
{"tag": "Category", "path": "*cgreq.Category", "type": "*constant", "value": "call", "mandatory": true},
|
||||
{"tag": "Account", "path": "*cgreq.Account", "type": "*variable", "value": "~*req.root.partial_cdr.Account", "mandatory": true},
|
||||
{"tag": "Destination", "path": "*cgreq.Destination", "type": "*variable", "value": "~*req.root.partial_cdr.Destination", "mandatory": true},
|
||||
{"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", "value": "~*req.root.partial_cdr.SetupTime", "mandatory": true},
|
||||
{"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", "value": "~*req.root.partial_cdr.AnswerTime", "mandatory": true},
|
||||
{"tag": "Usage", "path": "*cgreq.Usage", "type": "*usage_difference", "value": "~*req.root.partial_cdr.ReleaseTime;~*req.root.partial_cdr.AnswerTime", "mandatory": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "FWV1",
|
||||
|
||||
@@ -115,7 +115,7 @@ func (rdr *XMLFileER) Serve() (err error) {
|
||||
}
|
||||
|
||||
// processFile is called for each file in a directory and dispatches erEvents from it
|
||||
func (rdr *XMLFileER) processFile(fPath, fName string) (err error) {
|
||||
func (rdr *XMLFileER) processFile(fPath, fName string) error {
|
||||
if cap(rdr.conReqs) != 0 { // 0 goes for no limit
|
||||
processFile := <-rdr.conReqs // Queue here for maxOpenFiles
|
||||
defer func() { rdr.conReqs <- processFile }()
|
||||
@@ -123,16 +123,21 @@ func (rdr *XMLFileER) processFile(fPath, fName string) (err error) {
|
||||
absPath := path.Join(fPath, fName)
|
||||
utils.Logger.Info(
|
||||
fmt.Sprintf("<%s> parsing <%s>", utils.ERs, absPath))
|
||||
var file *os.File
|
||||
if file, err = os.Open(absPath); err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
doc, err := xmlquery.Parse(file)
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
var doc *xmlquery.Node
|
||||
doc, err = xmlquery.Parse(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var xmlElmts []*xmlquery.Node
|
||||
xmlElmts, err = xmlquery.QueryAll(doc, rdr.Config().XmlRootPath.AsString("/", true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xmlElmts := xmlquery.Find(doc, rdr.Config().XmlRootPath.AsString("/", true))
|
||||
rowNr := 0 // This counts the rows in the file, not really number of CDRs
|
||||
evsPosted := 0
|
||||
timeStart := time.Now()
|
||||
@@ -167,12 +172,12 @@ func (rdr *XMLFileER) processFile(fPath, fName string) (err error) {
|
||||
// Finished with file, move it to processed folder
|
||||
outPath := path.Join(rdr.Config().ProcessedPath, fName)
|
||||
if err = os.Rename(absPath, outPath); err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
utils.Logger.Info(
|
||||
fmt.Sprintf("%s finished processing file <%s>. Total records processed: %d, events posted: %d, run duration: %s",
|
||||
utils.ERs, absPath, rowNr, evsPosted, time.Now().Sub(timeStart)))
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ var (
|
||||
testXMLITLoadTPFromFolder,
|
||||
testXMLITHandleCdr1File,
|
||||
testXmlITAnalyseCDRs,
|
||||
testXMLITEmptyRootPathCase,
|
||||
testXMLITEmptyRootPathCaseCheckCDRs,
|
||||
testXMLITCleanupFiles,
|
||||
testXMLITKillEngine,
|
||||
}
|
||||
@@ -79,7 +81,7 @@ func testXMLITCreateCdrDirs(t *testing.T) {
|
||||
"/tmp/cdrs/out", "/tmp/ers_with_filters/in", "/tmp/ers_with_filters/out",
|
||||
"/tmp/xmlErs/in", "/tmp/xmlErs/out", "/tmp/fwvErs/in", "/tmp/fwvErs/out",
|
||||
"/tmp/partErs1/in", "/tmp/partErs1/out", "/tmp/partErs2/in", "/tmp/partErs2/out",
|
||||
"/tmp/flatstoreErs/in", "/tmp/flatstoreErs/out"} {
|
||||
"/tmp/flatstoreErs/in", "/tmp/flatstoreErs/out", "/tmp/xmlErs2/in", "/tmp/xmlErs2/out"} {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Fatal("Error removing folder: ", dir, err)
|
||||
}
|
||||
@@ -297,11 +299,55 @@ func testXmlITAnalyseCDRs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var xmlPartialCDR = `<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<root>
|
||||
<partial_cdr>
|
||||
<ID>1</ID>
|
||||
<Account>1001</Account>
|
||||
<Destination>1002</Destination>
|
||||
<Tenant>cgrates.org</Tenant>
|
||||
<ReleaseTime>20231011125833.158</ReleaseTime>
|
||||
</partial_cdr>
|
||||
<partial_cdr>
|
||||
<ID>2</ID>
|
||||
<RequestType>*postpaid</RequestType>
|
||||
<ToR>*voice</ToR>
|
||||
</partial_cdr>
|
||||
<partial_cdr>
|
||||
<TestCase>EmptyRootCDR</TestCase>
|
||||
<ID>3</ID>
|
||||
<Tenant>ignored</Tenant>
|
||||
<SetupTime>20231011125800</SetupTime>
|
||||
<AnswerTime>20231011125801</AnswerTime>
|
||||
</partial_cdr>
|
||||
</root>`
|
||||
|
||||
func testXMLITEmptyRootPathCase(t *testing.T) {
|
||||
fileName := "partial-cdr.xml"
|
||||
tmpFilePath := path.Join("/tmp", fileName)
|
||||
if err := os.WriteFile(tmpFilePath, []byte(xmlPartialCDR), 0644); err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if err := os.Rename(tmpFilePath, path.Join("/tmp/xmlErs2/in", fileName)); err != nil {
|
||||
t.Fatal("Error moving file to processing directory: ", err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func testXMLITEmptyRootPathCaseCheckCDRs(t *testing.T) {
|
||||
var reply []*engine.ExternalCDR
|
||||
if err := xmlRPC.Call(utils.APIerSv2GetCDRs, utils.RPCCDRsFilter{OriginIDs: []string{"EmptyRootCDR_2"}}, &reply); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if len(reply) != 3 {
|
||||
t.Error("Unexpected number of CDRs returned:", len(reply))
|
||||
}
|
||||
}
|
||||
|
||||
func testXMLITCleanupFiles(t *testing.T) {
|
||||
for _, dir := range []string{"/tmp/ers",
|
||||
"/tmp/ers2", "/tmp/init_session", "/tmp/terminate_session",
|
||||
"/tmp/cdrs", "/tmp/ers_with_filters", "/tmp/xmlErs", "/tmp/fwvErs",
|
||||
"/tmp/partErs1", "/tmp/partErs2", "tmp/flatstoreErs"} {
|
||||
"/tmp/partErs1", "/tmp/partErs2", "/tmp/flatstoreErs", "/tmp/xmlErs2"} {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Fatal("Error removing folder: ", dir, err)
|
||||
}
|
||||
|
||||
@@ -625,9 +625,12 @@ func TimeIs0h(t time.Time) bool {
|
||||
}
|
||||
|
||||
func ParseHierarchyPath(path string, sep string) HierarchyPath {
|
||||
if path == EmptyString {
|
||||
return nil
|
||||
}
|
||||
if sep == EmptyString {
|
||||
for _, sep = range []string{"/", NestingSep} {
|
||||
if idx := strings.Index(path, sep); idx != -1 {
|
||||
if strings.Contains(path, sep) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -639,22 +642,24 @@ func ParseHierarchyPath(path string, sep string) HierarchyPath {
|
||||
// HierarchyPath is used in various places to represent various path hierarchies (eg: in Diameter groups, XML trees)
|
||||
type HierarchyPath []string
|
||||
|
||||
func (h HierarchyPath) AsString(sep string, prefix bool) string {
|
||||
if len(h) == 0 {
|
||||
return EmptyString
|
||||
// AsString converts HierarchyPath to a string.
|
||||
func (hP HierarchyPath) AsString(sep string, prefix bool) string {
|
||||
var strHP strings.Builder
|
||||
|
||||
// If prefix is true and the HierarchyPath slice is empty, sep will be returned.
|
||||
if prefix {
|
||||
strHP.WriteString(sep)
|
||||
}
|
||||
retStr := EmptyString
|
||||
for idx, itm := range h {
|
||||
if idx == 0 {
|
||||
if prefix {
|
||||
retStr += sep
|
||||
}
|
||||
} else {
|
||||
retStr += sep
|
||||
if len(hP) == 0 {
|
||||
return strHP.String()
|
||||
}
|
||||
for i, elem := range hP {
|
||||
if i != 0 {
|
||||
strHP.WriteString(sep)
|
||||
}
|
||||
retStr += itm
|
||||
strHP.WriteString(elem)
|
||||
}
|
||||
return retStr
|
||||
return strHP.String()
|
||||
}
|
||||
|
||||
// Mask a number of characters in the suffix of the destination
|
||||
|
||||
Reference in New Issue
Block a user