diff --git a/apier/cdre.go b/apier/cdre.go index 110e04bc0..ba29fa0e6 100644 --- a/apier/cdre.go +++ b/apier/cdre.go @@ -25,10 +25,12 @@ import ( "github.com/cgrates/cgrates/utils" "os" "path" + "strconv" "strings" "time" ) +// Export Cdrs to file func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.ExportedFileCdrs) error { var tStart, tEnd time.Time var err error @@ -36,12 +38,6 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E if !utils.IsSliceMember(utils.CdreCdrFormats, cdrFormat) { return fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, "CdrFormat") } - exportedFields := self.Config.CdreExportedFields - if len(attr.ExportedFields) != 0 { - if exportedFields, err = config.ParseRSRFields(attr.ExportedFields); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - } if len(attr.TimeStart) != 0 { if tStart, err = utils.ParseTimeDetectLayout(attr.TimeStart); err != nil { return err @@ -52,38 +48,93 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E return err } } - cdrs, err := self.CdrDb.GetStoredCdrs(attr.MediationRunId, attr.CdrHost, attr.CdrSource, attr.ReqType, attr.Direction, + fileName := attr.ExportFileName + exportId := attr.ExportId + if len(exportId) == 0 { + exportId = strconv.FormatInt(time.Now().Unix(), 10) + } + roundDecimals := attr.RoundingDecimals + if roundDecimals == 0 { + roundDecimals = self.Config.RoundingDecimals + } + 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 { return err + } else if len(cdrs) == 0 { + *reply = utils.ExportedFileCdrs{"", 0} + return nil } - var fileName string - if cdrFormat == utils.CDRE_CSV && len(cdrs) != 0 { - fileName = path.Join(self.Config.CdreDir, fmt.Sprintf("cdrs_%d.csv", time.Now().Unix())) - fileOut, err := os.Create(fileName) + switch cdrFormat { + case utils.CDRE_CSV: + if len(fileName) == 0 { + fileName = fmt.Sprintf("cdre_%s.csv", exportId) + } + exportedFields := self.Config.CdreExportedFields + if len(attr.ExportTemplate) != 0 { + if exportedFields, err = config.ParseRSRFields(attr.ExportTemplate); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + } + if len(exportedFields) == 0 { + return fmt.Errorf("%s:ExportTemplate", utils.ERR_MANDATORY_IE_MISSING) + } + filePath := path.Join(self.Config.CdreDir, fileName) + fileOut, err := os.Create(filePath) if err != nil { return err - } else { - defer fileOut.Close() } - csvWriter := cdre.NewCsvCdrWriter(fileOut, self.Config.RoundingDecimals, exportedFields) + defer fileOut.Close() + csvWriter := cdre.NewCsvCdrWriter(fileOut, roundDecimals, exportedFields) for _, cdr := range cdrs { if err := csvWriter.WriteCdr(cdr); err != nil { - os.Remove(fileName) + os.Remove(filePath) return err } } csvWriter.Close() - if attr.RemoveFromDb { - cgrIds := make([]string, len(cdrs)) - for idx, cdr := range cdrs { - cgrIds[idx] = cdr.CgrId + case utils.CDRE_FIXED_WIDTH: + if len(fileName) == 0 { + fileName = fmt.Sprintf("cdre_%s.fwv", exportId) + } + exportTemplate := self.Config.CdreFWXmlTemplate + if len(attr.ExportTemplate) != 0 && self.Config.XmlCfgDocument != nil { + if xmlTemplate, err := self.Config.XmlCfgDocument.GetCdreFWCfg(attr.ExportTemplate[len(utils.XML_PROFILE_PREFIX):]); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } else if xmlTemplate != nil { + exportTemplate = xmlTemplate } - if err := self.CdrDb.RemStoredCdrs(cgrIds); err != nil { + } + if exportTemplate == nil { + return fmt.Errorf("%s:ExportTemplate", utils.ERR_MANDATORY_IE_MISSING) + } + filePath := path.Join(self.Config.CdreDir, fileName) + fileOut, err := os.Create(filePath) + if err != nil { + return err + } + defer fileOut.Close() + fww, _ := cdre.NewFWCdrWriter(self.LogDb, fileOut, exportTemplate, exportId, roundDecimals) + for _, cdr := range cdrs { + if err := fww.WriteCdr(cdr); err != nil { + os.Remove(filePath) return err } } + fww.Close() } *reply = utils.ExportedFileCdrs{fileName, len(cdrs)} return nil } + +// Remove Cdrs out of CDR storage +func (self *ApierV1) RemCdrs(attrs utils.AttrRemCdrs, reply *string) error { + if len(attrs.CgrIds) == 0 { + return fmt.Errorf("%s:CgrIds", utils.ERR_MANDATORY_IE_MISSING) + } + if err := self.CdrDb.RemStoredCdrs(attrs.CgrIds); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + *reply = "OK" + return nil +} diff --git a/cdre/fixedwidth.go b/cdre/fixedwidth.go index 6f5b00b1a..f5a10ce56 100644 --- a/cdre/fixedwidth.go +++ b/cdre/fixedwidth.go @@ -27,6 +27,7 @@ import ( "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" "io" + "os" "strconv" "strings" "time" @@ -50,12 +51,23 @@ const ( var err error +func NewFWCdrWriter(logDb engine.LogStorage, outFile *os.File, exportTpl *config.CgrXmlCdreFwCfg, exportId string, roundDecimals int) (*FixedWidthCdrWriter, error) { + return &FixedWidthCdrWriter{ + logDb: logDb, + writer: outFile, + exportTemplate: exportTpl, + exportId: exportId, + roundDecimals: roundDecimals, + header: &bytes.Buffer{}, + content: &bytes.Buffer{}, + trailer: &bytes.Buffer{}}, nil +} + type FixedWidthCdrWriter struct { logDb engine.LogStorage // Used to extract cost_details if these are requested writer io.Writer exportTemplate *config.CgrXmlCdreFwCfg exportId string // Unique identifier or this export - exportFileName string // If defined it will overwrite the file name roundDecimals int header, content, trailer *bytes.Buffer firstCdrTime, lastCdrTime time.Time diff --git a/config/config.go b/config/config.go index 82b69a1bd..ddfa8c395 100644 --- a/config/config.go +++ b/config/config.go @@ -283,7 +283,7 @@ func (self *CGRConfig) setDefaults() error { func (self *CGRConfig) checkConfigSanity() error { // Cdre sanity check for fixed_width - if self.CdreCdrFormat == utils.FIXED_WIDTH { + if self.CdreCdrFormat == utils.CDRE_FIXED_WIDTH { if self.XmlCfgDocument == nil { return errors.New("Need XmlConfigurationDocument for fixed_width cdr export") } else if self.CdreFWXmlTemplate == nil { @@ -492,7 +492,7 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { } 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.FIXED_WIDTH { // Csv most likely + if cfg.CdreCdrFormat != utils.CDRE_FIXED_WIDTH { // Csv most likely if extraFields, err := ParseRSRFields(exportTemplate); err != nil { return nil, errParse } else { diff --git a/config/config_test.go b/config/config_test.go index a528c3f8d..e9b83c4c9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -184,7 +184,7 @@ func TestSanityCheck(t *testing.T) { t.Error("Failed to detect config insanity") } cfg = &CGRConfig{} - cfg.CdreCdrFormat = utils.FIXED_WIDTH + cfg.CdreCdrFormat = utils.CDRE_FIXED_WIDTH if err := cfg.checkConfigSanity(); err == nil { t.Error("Failed to detect fixed_width dependency on xml configuration") } diff --git a/config/xmlconfig.go b/config/xmlconfig.go index 1ec79ec93..289375620 100644 --- a/config/xmlconfig.go +++ b/config/xmlconfig.go @@ -83,18 +83,18 @@ type CgrXmlCfgCdrField struct { Name string `xml:"name,attr"` Type string `xml:"type,attr"` Value string `xml:"value,attr"` - Width int `xml:"width,attr"` // Field width - Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright> - Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right> - Layout string `xml:"layout,attr"` // Eg. time format layout - Mandatory bool `xml:"layout,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported + Width int `xml:"width,attr"` // Field width + Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright> + Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right> + Layout string `xml:"layout,attr"` // Eg. time format layout + Mandatory bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported } // Avoid building from raw config string always, so build cache here func (xmlCfg *CgrXmlCfgDocument) cacheCdreFWCfgs() error { xmlCfg.cdrefws = make(map[string]*CgrXmlCdreFwCfg) for _, cfgInst := range xmlCfg.Configurations { - if cfgInst.Section == utils.CDRE || cfgInst.Type == utils.FIXED_WIDTH { + if cfgInst.Section == utils.CDRE || cfgInst.Type == utils.CDRE_FIXED_WIDTH { cdrefwCfg := new(CgrXmlCdreFwCfg) rawConfig := append([]byte(""), cfgInst.RawConfig...) // Encapsulate the rawConfig in one element so we can Unmarshall into one struct rawConfig = append(rawConfig, []byte("")...) diff --git a/config/xmlconfig_test.go b/config/xmlconfig_test.go index 43de27cd3..56ee6c43c 100644 --- a/config/xmlconfig_test.go +++ b/config/xmlconfig_test.go @@ -44,7 +44,7 @@ func TestParseXmlConfig(t *testing.T) { - + diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 10298939d..8a7f4c0a4 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -106,7 +106,7 @@ type CdrStorage interface { Storage SetCdr(utils.RawCDR) error SetRatedCdr(*utils.StoredCdr, string) error - GetStoredCdrs(string, string, string, string, string, string, string, string, string, string, time.Time, time.Time, bool, bool) ([]*utils.StoredCdr, error) + GetStoredCdrs([]string, string, string, string, string, string, string, string, string, string, string, time.Time, time.Time, bool, bool) ([]*utils.StoredCdr, error) RemStoredCdrs([]string) error } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 839f714df..2accf8dcc 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -577,97 +577,111 @@ func (self *SQLStorage) SetRatedCdr(storedCdr *utils.StoredCdr, extraInfo string // Return a slice of CDRs from storDb using optional filters.a // ignoreErr - do not consider cdrs with rating errors // ignoreRated - do not consider cdrs which were already rated, including here the ones with errors -func (self *SQLStorage) GetStoredCdrs(runId, cdrHost, cdrSource, reqType, direction, tenant, tor, account, subject, destPrefix string, +func (self *SQLStorage) GetStoredCdrs(cgrIds []string, runId string, cdrHost, cdrSource, reqType, direction, tenant, tor, account, subject, destPrefix string, timeStart, timeEnd time.Time, ignoreErr, ignoreRated bool) ([]*utils.StoredCdr, error) { var cdrs []*utils.StoredCdr q := fmt.Sprintf("SELECT %s.cgrid,accid,cdrhost,cdrsource,reqtype,direction,tenant,tor,account,%s.subject,destination,setup_time,answer_time,duration,extra_fields,runid,cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid", utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, utils.TBL_CDRS_PRIMARY, utils.TBL_RATED_CDRS) fltr := "" + if len(cgrIds) != 0 { + qIds := " (" + for idxId, cgrId := range cgrIds { + if idxId != 0 { + qIds += " OR" + } + qIds += fmt.Sprintf(" %s.cgrid='%s'", utils.TBL_CDRS_PRIMARY, cgrId) + } + qIds += " )" + if len(fltr) != 0 { + fltr += " AND" + } + fltr += qIds + } if len(runId) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" runid='%s'", runId) } if len(cdrHost) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" cdrhost='%s'", cdrHost) } if len(cdrSource) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" cdrsource='%s'", cdrSource) } if len(reqType) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" reqtype='%s'", reqType) } if len(direction) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" direction='%s'", direction) } if len(tenant) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" tenant='%s'", tenant) } if len(tor) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" tor='%s'", tor) } if len(account) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" account='%s'", account) } if len(subject) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" %s.subject='%s'", utils.TBL_CDRS_PRIMARY, subject) } if len(destPrefix) != 0 { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" destination LIKE '%s%%'", destPrefix) } if !timeStart.IsZero() { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" answer_time>='%s'", timeStart) } if !timeEnd.IsZero() { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } fltr += fmt.Sprintf(" answer_time<'%s'", timeEnd) } if ignoreRated { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } if ignoreErr { - fltr += "cost IS NULL" + fltr += " cost IS NULL" } else { - fltr += "(cost=-1 OR cost IS NULL)" + fltr += " (cost=-1 OR cost IS NULL)" } } else if ignoreErr { if len(fltr) != 0 { - fltr += " AND " + fltr += " AND" } - fltr += "(cost!=-1 OR cost IS NULL)" + fltr += " (cost!=-1 OR cost IS NULL)" } if len(fltr) != 0 { q += fmt.Sprintf(" WHERE %s", fltr) diff --git a/engine/storage_sql_local_test.go b/engine/storage_sql_local_test.go index 06b8dcd89..88c66aabf 100644 --- a/engine/storage_sql_local_test.go +++ b/engine/storage_sql_local_test.go @@ -213,93 +213,93 @@ func TestGetStoredCdrs(t *testing.T) { } var timeStart, timeEnd time.Time // All CDRs, no filter - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on runId - if storedCdrs, err := mysql.GetStoredCdrs(utils.DEFAULT_RUNID, "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, utils.DEFAULT_RUNID, "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on cdrHost - if storedCdrs, err := mysql.GetStoredCdrs("", "192.168.1.2", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "192.168.1.2", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on cdrSource - if storedCdrs, err := mysql.GetStoredCdrs("", "", "UNKNOWN", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "UNKNOWN", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on reqType - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "prepaid", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "prepaid", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on direction - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "*out", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "*out", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on tenant - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "itsyscom.com", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "itsyscom.com", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on tor - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "", "premium_call", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "", "premium_call", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on account - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "", "", "1002", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "", "", "1002", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 3 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on subject - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "", "", "", "1000", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "1000", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on ignoreErr - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, true, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, true, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 8 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on ignoreRated - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, true); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, true); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 5 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on timeStart timeStart = time.Date(2013, 11, 8, 8, 0, 0, 0, time.UTC) - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 5 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Filter on timeStart and timeEnd timeEnd = time.Date(2013, 12, 1, 8, 0, 0, 0, time.UTC) - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 2 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } // Combined filter - if storedCdrs, err := mysql.GetStoredCdrs("", "", "", "rated", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { + if storedCdrs, err := mysql.GetStoredCdrs(nil, "", "", "", "rated", "", "", "", "", "", "", timeStart, timeEnd, false, false); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 1 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) diff --git a/mediator/mediator.go b/mediator/mediator.go index 47781299b..229ae6b46 100644 --- a/mediator/mediator.go +++ b/mediator/mediator.go @@ -161,7 +161,7 @@ func (self *Mediator) RateCdr(dbcdr utils.RawCDR) error { } func (self *Mediator) RateCdrs(timeStart, timeEnd time.Time, rerateErrors, rerateRated bool) error { - cdrs, err := self.cdrDb.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, !rerateErrors, !rerateRated) + cdrs, err := self.cdrDb.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, !rerateErrors, !rerateRated) if err != nil { return err } diff --git a/mediator/mediator_local_test.go b/mediator/mediator_local_test.go index 771d75a56..b2616986b 100644 --- a/mediator/mediator_local_test.go +++ b/mediator/mediator_local_test.go @@ -150,12 +150,12 @@ func TestPostCdrs(t *testing.T) { } } time.Sleep(10 * time.Millisecond) // Give time for CDRs to reach database - if storedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, false); err != nil { + if storedCdrs, err := cdrStor.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, false); err != nil { t.Error(err) } else if len(storedCdrs) != 2 { // Make sure CDRs made it into StorDb t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(storedCdrs))) } - if nonErrorCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, false); err != nil { + if nonErrorCdrs, err := cdrStor.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, false); err != nil { t.Error(err) } else if len(nonErrorCdrs) != 0 { // Just two of them should be without errors t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(nonErrorCdrs))) @@ -178,12 +178,12 @@ func TestInjectCdrs(t *testing.T) { t.Error(err) } } - if storedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, false); err != nil { + if storedCdrs, err := cdrStor.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, false); err != nil { t.Error(err) } else if len(storedCdrs) != 4 { // Make sure CDRs made it into StorDb t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(storedCdrs))) } - if nonRatedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, true); err != nil { + if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, true); err != nil { t.Error(err) } else if len(nonRatedCdrs) != 2 { // Just two of them should be non-rated t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs))) @@ -215,12 +215,12 @@ func TestRateCdrs(t *testing.T) { } else if reply != utils.OK { t.Errorf("Unexpected reply: %s", reply) } - if nonRatedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, true); err != nil { + if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, true); err != nil { t.Error(err) } else if len(nonRatedCdrs) != 0 { // Just two of them should be non-rated t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs))) } - if errRatedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, true); err != nil { + if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, true); err != nil { t.Error(err) } else if len(errRatedCdrs) != 2 { // The first 2 with errors should be still there before rerating t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs))) @@ -230,7 +230,7 @@ func TestRateCdrs(t *testing.T) { } else if reply != utils.OK { t.Errorf("Unexpected reply: %s", reply) } - if errRatedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, true); err != nil { + if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, "", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, true); err != nil { t.Error(err) } else if len(errRatedCdrs) != 1 { // One CDR with errors should be fixed now by rerating t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs))) diff --git a/utils/apitpdata.go b/utils/apitpdata.go index bdfddcc88..469b535fe 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -316,23 +316,30 @@ type CachedItemAge struct { } type AttrExpFileCdrs struct { - CdrFormat string // Cdr output file format - ExportedFields string // Optional comma separated list of fields ot be exported in a CDR - MediationRunId string // If provided, it will filter on mediation runid - CdrHost string // If provided, it will filter cdrhost - CdrSource string // If provided, it will filter cdrsource - ReqType string // If provided, it will fiter reqtype - Direction string // If provided, it will fiter direction - Tenant string // If provided, it will filter tenant - Tor string // If provided, it will filter tor - Account string // If provided, it will filter account - Subject string // If provided, it will filter the rating subject - DestinationPrefix string // If provided, it will filter on destination prefix - TimeStart string // If provided, it will represent the starting of the CDRs interval (>=) - TimeEnd string // If provided, it will represent the end of the CDRs interval (<) - SkipErrors bool // Do not export errored CDRs - SkipRated bool // Do not export rated CDRs - RemoveFromDb bool // If true the CDRs will be also deleted after export + CdrFormat string // Cdr output file format + ExportId string // Optional exportid + 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 + 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 + CdrSource string // If provided, it will filter cdrsource + ReqType string // If provided, it will fiter reqtype + Direction string // If provided, it will fiter direction + Tenant string // If provided, it will filter tenant + Tor string // If provided, it will filter tor + Account string // If provided, it will filter account + Subject string // If provided, it will filter the rating subject + DestinationPrefix string // If provided, it will filter on destination prefix + TimeStart string // If provided, it will represent the starting of the CDRs interval (>=) + TimeEnd string // If provided, it will represent the end of the CDRs interval (<) + SkipErrors bool // Do not export errored CDRs + SkipRated bool // Do not export rated CDRs +} + +type AttrRemCdrs struct { + CgrIds []string // List of CgrIds to remove from storeDb } type ExportedFileCdrs struct { diff --git a/utils/consts.go b/utils/consts.go index 2f572e37d..f9d2d133b 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -91,11 +91,11 @@ const ( INTERNAL = "internal" ZERO_RATING_SUBJECT_PREFIX = "*zero" OK = "OK" - FIXED_WIDTH = "fixed_width" + CDRE_FIXED_WIDTH = "fixed_width" XML_PROFILE_PREFIX = "*xml:" CDRE = "cdre" ) var ( - CdreCdrFormats = []string{CDRE_CSV, CDRE_DRYRUN} + CdreCdrFormats = []string{CDRE_CSV, CDRE_DRYRUN, CDRE_FIXED_WIDTH} )