Adding MaskDestination support in cdrexporter

This commit is contained in:
DanB
2014-03-29 14:01:14 +01:00
parent 9b1ba8b6c7
commit 65d23a1eb5
14 changed files with 182 additions and 58 deletions

View File

@@ -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 {

View File

@@ -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())

View File

@@ -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())
}
}

View File

@@ -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))

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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 (