mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Adding MaskDestination support in cdrexporter
This commit is contained in:
@@ -57,6 +57,14 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
|
||||
if roundDecimals == 0 {
|
||||
roundDecimals = self.Config.RoundingDecimals
|
||||
}
|
||||
maskDestId := attr.MaskDestinationId
|
||||
if len(maskDestId) == 0 {
|
||||
maskDestId = self.Config.CdreMaskDestId
|
||||
}
|
||||
maskLen := attr.MaskLength
|
||||
if maskLen == 0 {
|
||||
maskLen = self.Config.CdreMaskLength
|
||||
}
|
||||
cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunId, attr.CdrHost, attr.CdrSource, attr.ReqType, attr.Direction,
|
||||
attr.Tenant, attr.Tor, attr.Account, attr.Subject, attr.DestinationPrefix, tStart, tEnd, attr.SkipErrors, attr.SkipRated)
|
||||
if err != nil {
|
||||
@@ -124,7 +132,7 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
|
||||
return err
|
||||
}
|
||||
defer fileOut.Close()
|
||||
fww, _ := cdre.NewFWCdrWriter(self.LogDb, fileOut, exportTemplate, exportId, roundDecimals)
|
||||
fww, _ := cdre.NewFWCdrWriter(self.LogDb, fileOut, exportTemplate, exportId, roundDecimals, maskDestId, maskLen)
|
||||
exportedIds := make([]string, 0)
|
||||
unexportedIds := make(map[string]string)
|
||||
for _, cdr := range cdrs {
|
||||
|
||||
@@ -315,8 +315,8 @@ func TestMaxCallDuration(t *testing.T) {
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "1002",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
|
||||
}
|
||||
var remainingDurationFloat float64
|
||||
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
|
||||
@@ -334,8 +334,8 @@ func TestMaxCallDuration(t *testing.T) {
|
||||
Subject: "1002",
|
||||
Account: "1002",
|
||||
Destination: "1001",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
|
||||
}
|
||||
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
|
||||
t.Error(err)
|
||||
@@ -352,8 +352,8 @@ func TestMaxCallDuration(t *testing.T) {
|
||||
Subject: "1006",
|
||||
Account: "1006",
|
||||
Destination: "1001",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
|
||||
}
|
||||
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
|
||||
t.Error(err)
|
||||
@@ -371,8 +371,8 @@ func TestMaxCallDuration(t *testing.T) {
|
||||
Subject: "1007",
|
||||
Account: "1007",
|
||||
Destination: "1001",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
|
||||
}
|
||||
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
|
||||
t.Error(err)
|
||||
@@ -397,8 +397,8 @@ func TestMaxDebit1001(t *testing.T) {
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "1002",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(time.Duration(10) * time.Second),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(time.Duration(10) * time.Second),
|
||||
}
|
||||
if err := rater.Call("Responder.MaxDebit", cd, cc); err != nil {
|
||||
t.Error(err.Error())
|
||||
@@ -436,8 +436,8 @@ func TestMaxDebit1007(t *testing.T) {
|
||||
Subject: "1007",
|
||||
Account: "1007",
|
||||
Destination: "1002",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(time.Duration(10) * time.Second),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(time.Duration(10) * time.Second),
|
||||
}
|
||||
if err := rater.Call("Responder.MaxDebit", cd, cc); err != nil {
|
||||
t.Error(err.Error())
|
||||
|
||||
@@ -46,17 +46,21 @@ const (
|
||||
META_NRCDRS = "cdrs_number"
|
||||
META_DURCDRS = "cdrs_duration"
|
||||
META_COSTCDRS = "cdrs_cost"
|
||||
META_MASKDESTINATION = "mask_destination"
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
func NewFWCdrWriter(logDb engine.LogStorage, outFile *os.File, exportTpl *config.CgrXmlCdreFwCfg, exportId string, roundDecimals int) (*FixedWidthCdrWriter, error) {
|
||||
func NewFWCdrWriter(logDb engine.LogStorage, outFile *os.File, exportTpl *config.CgrXmlCdreFwCfg, exportId string,
|
||||
roundDecimals int, maskDestId string, maskLen int) (*FixedWidthCdrWriter, error) {
|
||||
return &FixedWidthCdrWriter{
|
||||
logDb: logDb,
|
||||
writer: outFile,
|
||||
exportTemplate: exportTpl,
|
||||
exportId: exportId,
|
||||
roundDecimals: roundDecimals,
|
||||
maskDestId: maskDestId,
|
||||
maskLen: maskLen,
|
||||
header: &bytes.Buffer{},
|
||||
content: &bytes.Buffer{},
|
||||
trailer: &bytes.Buffer{}}, nil
|
||||
@@ -68,6 +72,8 @@ type FixedWidthCdrWriter struct {
|
||||
exportTemplate *config.CgrXmlCdreFwCfg
|
||||
exportId string // Unique identifier or this export
|
||||
roundDecimals int
|
||||
maskDestId string
|
||||
maskLen int
|
||||
header, content, trailer *bytes.Buffer
|
||||
firstCdrATime, lastCdrATime time.Time
|
||||
numberOfRecords int
|
||||
@@ -77,8 +83,8 @@ type FixedWidthCdrWriter struct {
|
||||
|
||||
// Return Json marshaled callCost attached to
|
||||
// Keep it separately so we test only this part in local tests
|
||||
func (fww *FixedWidthCdrWriter) getCdrCostDetails(cgrId, runId string) (string, error) {
|
||||
cc, err := fww.logDb.GetCallCostLog(cgrId, "", runId)
|
||||
func (fwv *FixedWidthCdrWriter) getCdrCostDetails(cgrId, runId string) (string, error) {
|
||||
cc, err := fwv.logDb.GetCallCostLog(cgrId, "", runId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if cc == nil {
|
||||
@@ -88,8 +94,15 @@ func (fww *FixedWidthCdrWriter) getCdrCostDetails(cgrId, runId string) (string,
|
||||
return string(ccJson), nil
|
||||
}
|
||||
|
||||
func (fwv *FixedWidthCdrWriter) maskedDestination(destination string) bool {
|
||||
if len(fwv.maskDestId) != 0 && engine.CachedDestHasPrefix(fwv.maskDestId, destination) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Extracts the value specified by cfgHdr out of cdr
|
||||
func (fww *FixedWidthCdrWriter) cdrFieldValue(cdr *utils.StoredCdr, cfgHdr, layout string) (string, error) {
|
||||
func (fwv *FixedWidthCdrWriter) cdrFieldValue(cdr *utils.StoredCdr, cfgHdr, layout string) (string, error) {
|
||||
rsrField, err := utils.NewRSRField(cfgHdr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -99,37 +112,47 @@ func (fww *FixedWidthCdrWriter) cdrFieldValue(cdr *utils.StoredCdr, cfgHdr, layo
|
||||
var cdrVal string
|
||||
switch rsrField.Id {
|
||||
case COST_DETAILS: // Special case when we need to further extract cost_details out of logDb
|
||||
if cdrVal, err = fww.getCdrCostDetails(cdr.CgrId, cdr.MediationRunId); err != nil {
|
||||
if cdrVal, err = fwv.getCdrCostDetails(cdr.CgrId, cdr.MediationRunId); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case utils.COST:
|
||||
cdrVal = cdr.FormatCost(fww.roundDecimals)
|
||||
cdrVal = cdr.FormatCost(fwv.roundDecimals)
|
||||
case utils.SETUP_TIME:
|
||||
cdrVal = cdr.SetupTime.Format(layout)
|
||||
case utils.ANSWER_TIME: // Format time based on layout
|
||||
cdrVal = cdr.AnswerTime.Format(layout)
|
||||
case utils.DESTINATION:
|
||||
cdrVal = cdr.ExportFieldValue(utils.DESTINATION)
|
||||
if fwv.maskLen != -1 && fwv.maskedDestination(cdrVal) {
|
||||
cdrVal = MaskDestination(cdrVal, fwv.maskLen)
|
||||
}
|
||||
default:
|
||||
cdrVal = cdr.ExportFieldValue(rsrField.Id)
|
||||
}
|
||||
return rsrField.ParseValue(cdrVal), nil
|
||||
}
|
||||
|
||||
func (fww *FixedWidthCdrWriter) metaHandler(tag, layout string) (string, error) {
|
||||
func (fwv *FixedWidthCdrWriter) metaHandler(tag, arg string) (string, error) {
|
||||
switch tag {
|
||||
case META_EXPORTID:
|
||||
return fww.exportId, nil
|
||||
return fwv.exportId, nil
|
||||
case META_TIMENOW:
|
||||
return time.Now().Format(layout), nil
|
||||
return time.Now().Format(arg), nil
|
||||
case META_FIRSTCDRATIME:
|
||||
return fww.firstCdrATime.Format(layout), nil
|
||||
return fwv.firstCdrATime.Format(arg), nil
|
||||
case META_LASTCDRATIME:
|
||||
return fww.lastCdrATime.Format(layout), nil
|
||||
return fwv.lastCdrATime.Format(arg), nil
|
||||
case META_NRCDRS:
|
||||
return strconv.Itoa(fww.numberOfRecords), nil
|
||||
return strconv.Itoa(fwv.numberOfRecords), nil
|
||||
case META_DURCDRS:
|
||||
return strconv.FormatFloat(fww.totalDuration.Seconds(), 'f', -1, 64), nil
|
||||
return strconv.FormatFloat(fwv.totalDuration.Seconds(), 'f', -1, 64), nil
|
||||
case META_COSTCDRS:
|
||||
return strconv.FormatFloat(utils.Round(fww.totalCost, fww.roundDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil
|
||||
return strconv.FormatFloat(utils.Round(fwv.totalCost, fwv.roundDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil
|
||||
case META_MASKDESTINATION:
|
||||
if fwv.maskedDestination(arg) {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
default:
|
||||
return "", fmt.Errorf("Unsupported METATAG: %s", tag)
|
||||
}
|
||||
@@ -137,9 +160,9 @@ func (fww *FixedWidthCdrWriter) metaHandler(tag, layout string) (string, error)
|
||||
}
|
||||
|
||||
// Writes the header into it's buffer
|
||||
func (fww *FixedWidthCdrWriter) ComposeHeader() error {
|
||||
func (fwv *FixedWidthCdrWriter) ComposeHeader() error {
|
||||
header := ""
|
||||
for _, cfgFld := range fww.exportTemplate.Header.Fields {
|
||||
for _, cfgFld := range fwv.exportTemplate.Header.Fields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER:
|
||||
@@ -148,7 +171,7 @@ func (fww *FixedWidthCdrWriter) ComposeHeader() error {
|
||||
case CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case METATAG:
|
||||
outVal, err = fww.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
outVal, err = fwv.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
|
||||
}
|
||||
@@ -167,14 +190,14 @@ func (fww *FixedWidthCdrWriter) ComposeHeader() error {
|
||||
return nil
|
||||
}
|
||||
header += "\n" // Done with cdr, postpend new line char
|
||||
fww.header.WriteString(header)
|
||||
fwv.header.WriteString(header)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Writes the trailer into it's buffer
|
||||
func (fww *FixedWidthCdrWriter) ComposeTrailer() error {
|
||||
func (fwv *FixedWidthCdrWriter) ComposeTrailer() error {
|
||||
trailer := ""
|
||||
for _, cfgFld := range fww.exportTemplate.Trailer.Fields {
|
||||
for _, cfgFld := range fwv.exportTemplate.Trailer.Fields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER:
|
||||
@@ -183,7 +206,7 @@ func (fww *FixedWidthCdrWriter) ComposeTrailer() error {
|
||||
case CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case METATAG:
|
||||
outVal, err = fww.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
outVal, err = fwv.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
|
||||
}
|
||||
@@ -202,18 +225,18 @@ func (fww *FixedWidthCdrWriter) ComposeTrailer() error {
|
||||
return nil
|
||||
}
|
||||
trailer += "\n" // Done with cdr, postpend new line char
|
||||
fww.trailer.WriteString(trailer)
|
||||
fwv.trailer.WriteString(trailer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write individual cdr into content buffer, build stats
|
||||
func (fww *FixedWidthCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
func (fwv *FixedWidthCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
if cdr == nil || len(cdr.CgrId) == 0 { // We do not export empty CDRs
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
cdrRow := ""
|
||||
for _, cfgFld := range fww.exportTemplate.Content.Fields {
|
||||
for _, cfgFld := range fwv.exportTemplate.Content.Fields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER:
|
||||
@@ -222,15 +245,17 @@ func (fww *FixedWidthCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
case CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case CDRFIELD:
|
||||
outVal, err = fww.cdrFieldValue(cdr, cfgFld.Value, cfgFld.Layout)
|
||||
outVal, err = fwv.cdrFieldValue(cdr, cfgFld.Value, cfgFld.Layout)
|
||||
case CONCATENATED_CDRFIELD:
|
||||
for _, fld := range strings.Split(cfgFld.Value, ",") {
|
||||
if fldOut, err := fww.cdrFieldValue(cdr, fld, cfgFld.Layout); err != nil {
|
||||
if fldOut, err := fwv.cdrFieldValue(cdr, fld, cfgFld.Layout); err != nil {
|
||||
break // The error will be reported bellow
|
||||
} else {
|
||||
outVal += fldOut
|
||||
}
|
||||
}
|
||||
case METATAG:
|
||||
outVal, err = fwv.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
}
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s and runid: %s, error: %s", cdr.CgrId, cdr.MediationRunId, err.Error()))
|
||||
@@ -247,29 +272,29 @@ func (fww *FixedWidthCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
return nil
|
||||
}
|
||||
cdrRow += "\n" // Done with cdr, postpend new line char
|
||||
fww.content.WriteString(cdrRow)
|
||||
fwv.content.WriteString(cdrRow)
|
||||
// Done with writing content, compute stats here
|
||||
if fww.firstCdrATime.IsZero() || cdr.AnswerTime.Before(fww.firstCdrATime) {
|
||||
fww.firstCdrATime = cdr.AnswerTime
|
||||
if fwv.firstCdrATime.IsZero() || cdr.AnswerTime.Before(fwv.firstCdrATime) {
|
||||
fwv.firstCdrATime = cdr.AnswerTime
|
||||
}
|
||||
if cdr.AnswerTime.After(fww.lastCdrATime) {
|
||||
fww.lastCdrATime = cdr.AnswerTime
|
||||
if cdr.AnswerTime.After(fwv.lastCdrATime) {
|
||||
fwv.lastCdrATime = cdr.AnswerTime
|
||||
}
|
||||
fww.numberOfRecords += 1
|
||||
fww.totalDuration += cdr.Duration
|
||||
fww.totalCost += cdr.Cost
|
||||
fww.totalCost = utils.Round(fww.totalCost, fww.roundDecimals, utils.ROUNDING_MIDDLE)
|
||||
fwv.numberOfRecords += 1
|
||||
fwv.totalDuration += cdr.Duration
|
||||
fwv.totalCost += cdr.Cost
|
||||
fwv.totalCost = utils.Round(fwv.totalCost, fwv.roundDecimals, utils.ROUNDING_MIDDLE)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fww *FixedWidthCdrWriter) Close() {
|
||||
if fww.exportTemplate.Header != nil {
|
||||
fww.ComposeHeader()
|
||||
func (fwv *FixedWidthCdrWriter) Close() {
|
||||
if fwv.exportTemplate.Header != nil {
|
||||
fwv.ComposeHeader()
|
||||
}
|
||||
if fww.exportTemplate.Trailer != nil {
|
||||
fww.ComposeTrailer()
|
||||
if fwv.exportTemplate.Trailer != nil {
|
||||
fwv.ComposeTrailer()
|
||||
}
|
||||
for _, buf := range []*bytes.Buffer{fww.header, fww.content, fww.trailer} {
|
||||
fww.writer.Write(buf.Bytes())
|
||||
for _, buf := range []*bytes.Buffer{fwv.header, fwv.content, fwv.trailer} {
|
||||
fwv.writer.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ var contentCfgFlds = []*config.CgrXmlCfgCdrField{
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8},
|
||||
&config.CgrXmlCfgCdrField{Name: "TerminationCode", Type: CONCATENATED_CDRFIELD, Value: "operator,product", Width: 5, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Cost", Type: CDRFIELD, Value: utils.COST, Width: 9, Padding: "zeroleft"},
|
||||
&config.CgrXmlCfgCdrField{Name: "DestinationPrivacy", Type: CDRFIELD, Value: "destination_privacy", Width: 1, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "DestinationPrivacy", Type: METATAG, Value: META_MASKDESTINATION, Width: 1},
|
||||
}
|
||||
|
||||
var trailerCfgFlds = []*config.CgrXmlCfgCdrField{
|
||||
@@ -91,7 +91,7 @@ func TestWriteCdr(t *testing.T) {
|
||||
if err := fwWriter.WriteCdr(cdr); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eContentOut := "201001 1001 1002 0211 07111308420010 1 3dsafdsaf 0002.3457 \n"
|
||||
eContentOut := "201001 1001 1002 0211 07111308420010 1 3dsafdsaf 0002.34570\n"
|
||||
contentOut := fwWriter.content.String()
|
||||
if len(contentOut) != 145 {
|
||||
t.Error("Unexpected content length", len(contentOut))
|
||||
|
||||
@@ -21,6 +21,7 @@ package cdre
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
// Used as generic function logic for various fields
|
||||
@@ -71,3 +72,18 @@ func FmtFieldWidth(source string, width int, strip, padding string, mandatory bo
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
// Mask a number of characters in the suffix of the destination
|
||||
func MaskDestination(dest string, maskLen int) string {
|
||||
destLen := len(dest)
|
||||
if maskLen < 0 {
|
||||
return dest
|
||||
} else if maskLen > destLen {
|
||||
maskLen = destLen
|
||||
}
|
||||
dest = dest[:destLen-maskLen]
|
||||
for i := 0; i < maskLen; i++ {
|
||||
dest += utils.MASK_CHAR
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
@@ -114,3 +114,20 @@ func TestPaddingNotAllowed(t *testing.T) {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskDestination(t *testing.T) {
|
||||
dest := "+4986517174963"
|
||||
if destMasked := MaskDestination(dest, 3); destMasked != "+4986517174***" {
|
||||
t.Error("Unexpected mask applied", destMasked)
|
||||
}
|
||||
if destMasked := MaskDestination(dest, -1); destMasked != dest {
|
||||
t.Error("Negative maskLen should not modify destination", destMasked)
|
||||
}
|
||||
if destMasked := MaskDestination(dest, 0); destMasked != dest {
|
||||
t.Error("Zero maskLen should not modify destination", destMasked)
|
||||
}
|
||||
if destMasked := MaskDestination(dest, 100); destMasked != "**************" {
|
||||
t.Error("High maskLen should return complete mask", destMasked)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -91,6 +91,8 @@ type CGRConfig struct {
|
||||
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>
|
||||
CdreMaskDestId string // Id of the destination list to be masked in CDRs
|
||||
CdreMaskLength int // Number of digits to mask in the destination suffix if destination is in the MaskDestinationdsId
|
||||
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
|
||||
@@ -197,6 +199,8 @@ func (self *CGRConfig) setDefaults() error {
|
||||
self.CDRSExtraFields = []*utils.RSRField{}
|
||||
self.CDRSMediator = ""
|
||||
self.CdreCdrFormat = "csv"
|
||||
self.CdreMaskDestId = ""
|
||||
self.CdreMaskLength = 0
|
||||
self.CdreDir = "/var/log/cgrates/cdr/cdre"
|
||||
self.CdrcEnabled = false
|
||||
self.CdrcCdrs = utils.INTERNAL
|
||||
@@ -488,6 +492,12 @@ 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", "mask_destination_id"); hasOpt {
|
||||
cfg.CdreMaskDestId, _ = c.GetString("cdre", "mask_destination_id")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "mask_length"); hasOpt {
|
||||
cfg.CdreMaskLength, _ = c.GetInt("cdre", "mask_length")
|
||||
}
|
||||
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.CDRE_FIXED_WIDTH { // Csv most likely
|
||||
|
||||
@@ -82,6 +82,8 @@ func TestDefaults(t *testing.T) {
|
||||
eCfg.CDRSExtraFields = []*utils.RSRField{}
|
||||
eCfg.CDRSMediator = ""
|
||||
eCfg.CdreCdrFormat = "csv"
|
||||
eCfg.CdreMaskDestId = ""
|
||||
eCfg.CdreMaskLength = 0
|
||||
eCfg.CdreDir = "/var/log/cgrates/cdr/cdre"
|
||||
eCfg.CdrcEnabled = false
|
||||
eCfg.CdrcCdrs = utils.INTERNAL
|
||||
@@ -236,6 +238,8 @@ func TestConfigFromFile(t *testing.T) {
|
||||
eCfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
|
||||
eCfg.CDRSMediator = "test"
|
||||
eCfg.CdreCdrFormat = "test"
|
||||
eCfg.CdreMaskDestId = "test"
|
||||
eCfg.CdreMaskLength = 99
|
||||
eCfg.CdreExportedFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
|
||||
eCfg.CdreDir = "test"
|
||||
eCfg.CdrcEnabled = true
|
||||
|
||||
@@ -49,6 +49,8 @@ mediator = test # Address where to reach the Mediator. Empty for disabling me
|
||||
|
||||
[cdre]
|
||||
cdr_format = test # Exported CDRs format <csv>
|
||||
mask_destination_id = test # Destination id containing called addresses to be masked on export
|
||||
mask_length = 99 # Length of the destination suffix to be masked
|
||||
export_dir = test # Path where the exported CDRs will be placed
|
||||
export_template = test # List of fields in the exported CDRs
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
|
||||
[cdre]
|
||||
# cdr_format = csv # Exported CDRs format <csv>
|
||||
# mask_destination_id = # Destination id containing called addresses to be masked on export
|
||||
# mask_length = 0 # Length of the destination suffix to be masked
|
||||
# export_dir = /var/log/cgrates/cdr/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>
|
||||
|
||||
@@ -20,6 +20,7 @@ package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/cgrates/cgrates/cache2go"
|
||||
"strings"
|
||||
|
||||
"github.com/cgrates/cgrates/history"
|
||||
@@ -68,3 +69,15 @@ func (d *Destination) GetHistoryRecord() history.Record {
|
||||
Payload: js,
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse search in cache to see if prefix belongs to destination id
|
||||
func CachedDestHasPrefix(destId, prefix string) bool {
|
||||
if cached, err := cache2go.GetCached(DESTINATION_PREFIX + prefix); err == nil {
|
||||
for _, cachedDstId := range cached.([]string) {
|
||||
if destId == cachedDstId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -101,6 +101,30 @@ func TestDestinationGetNotExistsCache(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCachedDestHasPrefix(t *testing.T) {
|
||||
if !CachedDestHasPrefix("NAT", "0256") {
|
||||
t.Error("Could not find prefix in destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCachedDestHasWrongPrefix(t *testing.T) {
|
||||
if CachedDestHasPrefix("NAT", "771") {
|
||||
t.Error("Prefix should not belong to destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonCachedDestRightPrefix(t *testing.T) {
|
||||
if CachedDestHasPrefix("FAKE", "0256") {
|
||||
t.Error("Destination should not belong to prefix")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonCachedDestWrongPrefix(t *testing.T) {
|
||||
if CachedDestHasPrefix("FAKE", "771") {
|
||||
t.Error("Both arguments should be fake")
|
||||
}
|
||||
}
|
||||
|
||||
/********************************* Benchmarks **********************************/
|
||||
|
||||
func BenchmarkDestinationStorageStoreRestore(b *testing.B) {
|
||||
|
||||
@@ -321,6 +321,8 @@ type AttrExpFileCdrs struct {
|
||||
ExportFileName string // If provided the output filename will be set to this
|
||||
ExportTemplate string // Exported fields template <""|fld1,fld2|*xml:instance_name>
|
||||
RoundingDecimals int // Overwrite configured roundDecimals with this dynamically
|
||||
MaskDestinationId string // Overwrite configured MaskDestId
|
||||
MaskLength int // Overwrite configured MaskLength
|
||||
CgrIds []string // If provided, it will filter based on the cgrids present in list
|
||||
MediationRunId string // If provided, it will filter on mediation runid
|
||||
CdrHost string // If provided, it will filter cdrhost
|
||||
|
||||
@@ -91,9 +91,10 @@ const (
|
||||
INTERNAL = "internal"
|
||||
ZERO_RATING_SUBJECT_PREFIX = "*zero"
|
||||
OK = "OK"
|
||||
CDRE_FIXED_WIDTH = "fixed_width"
|
||||
CDRE_FIXED_WIDTH = "fwv"
|
||||
XML_PROFILE_PREFIX = "*xml:"
|
||||
CDRE = "cdre"
|
||||
MASK_CHAR = "*"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user