diff --git a/cdrc/cdrc.go b/cdrc/cdrc.go index d24b815ae..1c5cbc034 100644 --- a/cdrc/cdrc.go +++ b/cdrc/cdrc.go @@ -90,6 +90,7 @@ func (self *Cdrc) parseFieldsConfig() error { utils.ACCOUNT: self.cgrCfg.CdrcAccountField, utils.SUBJECT: self.cgrCfg.CdrcSubjectField, utils.DESTINATION: self.cgrCfg.CdrcDestinationField, + utils.SETUP_TIME: self.cgrCfg.CdrcSetupTimeField, utils.ANSWER_TIME: self.cgrCfg.CdrcAnswerTimeField, utils.DURATION: self.cgrCfg.CdrcDurationField, } @@ -153,6 +154,10 @@ func (self *Cdrc) recordAsStoredCdr(record []string) (*utils.StoredCdr, error) { ratedCdr.Subject = fieldVal case utils.DESTINATION: ratedCdr.Destination = fieldVal + case utils.SETUP_TIME: + if ratedCdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil { + return nil, fmt.Errorf("Cannot parse answer time field, err: %s", err.Error()) + } case utils.ANSWER_TIME: if ratedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil { return nil, fmt.Errorf("Cannot parse answer time field, err: %s", err.Error()) diff --git a/cdrc/cdrc_test.go b/cdrc/cdrc_test.go index 0a35613f1..72223f0dd 100644 --- a/cdrc/cdrc_test.go +++ b/cdrc/cdrc_test.go @@ -58,7 +58,7 @@ func TestParseFieldsConfig(t *testing.T) { func TestRecordAsStoredCdr(t *testing.T) { cgrConfig, _ := config.NewDefaultCGRConfig() - cgrConfig.CdrcExtraFields = []string{"supplier:10"} + cgrConfig.CdrcExtraFields = []string{"supplier:11"} cdrc := &Cdrc{cgrCfg: cgrConfig} if err := cdrc.parseFieldsConfig(); err != nil { t.Error("Failed parsing default fieldIndexesFromConfig", err) @@ -68,7 +68,8 @@ func TestRecordAsStoredCdr(t *testing.T) { if err == nil { t.Error("Failed to corectly detect missing fields from record") } - cdrRow = []string{"acc1", "prepaid", "*out", "cgrates.org", "call", "1001", "1001", "+4986517174963", "2013-02-03 19:54:00", "62", "supplier1", "172.16.1.1"} + cdrRow = []string{"acc1", "prepaid", "*out", "cgrates.org", "call", "1001", "1001", "+4986517174963", "2013-02-03 19:50:00", "2013-02-03 19:54:00", "62", + "supplier1", "172.16.1.1"} rtCdr, err := cdrc.recordAsStoredCdr(cdrRow) if err != nil { t.Error("Failed to parse CDR in rated cdr", err) @@ -84,6 +85,7 @@ func TestRecordAsStoredCdr(t *testing.T) { Account: cdrRow[5], Subject: cdrRow[6], Destination: cdrRow[7], + SetupTime: time.Date(2013, 2, 3, 19, 50, 0, 0, time.UTC), AnswerTime: time.Date(2013, 2, 3, 19, 54, 0, 0, time.UTC), Duration: time.Duration(62) * time.Second, ExtraFields: map[string]string{"supplier": "supplier1"}, diff --git a/cdrexporter/csv.go b/cdrexporter/csv.go index 3bfb47903..a85cfcb10 100644 --- a/cdrexporter/csv.go +++ b/cdrexporter/csv.go @@ -38,7 +38,7 @@ func NewCsvCdrWriter(writer io.Writer, roundDecimals int, extraFields []string) func (dcw *CsvCdrWriter) Write(cdr *utils.StoredCdr) error { primaryFields := []string{cdr.CgrId, cdr.MediationRunId, cdr.AccId, cdr.CdrHost, cdr.ReqType, cdr.Direction, cdr.Tenant, cdr.TOR, cdr.Account, cdr.Subject, - cdr.Destination, cdr.AnswerTime.String(), strconv.Itoa(int(cdr.Duration)), strconv.FormatFloat(cdr.Cost, 'f', dcw.roundDecimals, 64)} + cdr.Destination, cdr.SetupTime.String(), cdr.AnswerTime.String(), strconv.Itoa(int(cdr.Duration)), strconv.FormatFloat(cdr.Cost, 'f', dcw.roundDecimals, 64)} if len(dcw.extraFields) == 0 { dcw.extraFields = utils.MapKeys(cdr.ExtraFields) sort.Strings(dcw.extraFields) // Controlled order in case of dynamic extra fields diff --git a/cdrexporter/csv_test.go b/cdrexporter/csv_test.go index 1b06019b2..a3941f94c 100644 --- a/cdrexporter/csv_test.go +++ b/cdrexporter/csv_test.go @@ -30,12 +30,13 @@ func TestCsvCdrWriter(t *testing.T) { writer := &bytes.Buffer{} csvCdrWriter := NewCsvCdrWriter(writer, 4, []string{"extra3", "extra1"}) ratedCdr := &utils.StoredCdr{CgrId: utils.FSCgrId("dsafdsaf"), AccId: "dsafdsaf", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", - TOR: "call", Account: "1001", Subject: "1001", Destination: "1002", AnswerTime: time.Unix(1383813746, 0).UTC(), Duration: 10, MediationRunId: utils.DEFAULT_RUNID, + TOR: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), + Duration: 10, MediationRunId: utils.DEFAULT_RUNID, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, Cost: 1.01, } csvCdrWriter.Write(ratedCdr) csvCdrWriter.Close() - expected := "b18944ef4dc618569f24c27b9872827a242bad0c,default,dsafdsaf,192.168.1.1,rated,*out,cgrates.org,call,1001,1001,1002,2013-11-07 08:42:26 +0000 UTC,10,1.0100,val_extra3,val_extra1" + expected := "b18944ef4dc618569f24c27b9872827a242bad0c,default,dsafdsaf,192.168.1.1,rated,*out,cgrates.org,call,1001,1001,1002,2013-11-07 08:42:25 +0000 UTC,2013-11-07 08:42:26 +0000 UTC,10,1.0100,val_extra3,val_extra1" result := strings.TrimSpace(writer.String()) if result != expected { t.Errorf("Expected %s received %s.", expected, result) diff --git a/cdrs/fscdr.go b/cdrs/fscdr.go index 3146c3526..cac1be61e 100644 --- a/cdrs/fscdr.go +++ b/cdrs/fscdr.go @@ -41,6 +41,7 @@ const ( FS_CSTMID = "cgr_tenant" FS_CALL_DEST_NR = "dialed_extension" FS_PARK_TIME = "start_epoch" + FS_SETUP_TIME = "start_epoch" FS_ANSWER_TIME = "answer_epoch" FS_HANGUP_TIME = "end_epoch" FS_DURATION = "billsec" @@ -114,6 +115,12 @@ func (fsCdr FSCdr) GetExtraFields() map[string]string { } return extraFields } +func (fsCdr FSCdr) GetSetupTime() (t time.Time, err error) { + //ToDo: Make sure we work with UTC instead of local time + at, err := strconv.ParseInt(fsCdr[FS_SETUP_TIME], 0, 64) + t = time.Unix(at, 0) + return +} func (fsCdr FSCdr) GetAnswerTime() (t time.Time, err error) { //ToDo: Make sure we work with UTC instead of local time at, err := strconv.ParseInt(fsCdr[FS_ANSWER_TIME], 0, 64) @@ -163,13 +170,13 @@ func (fsCdr FSCdr) Restore(input string) error { } // Used in extra mediation -func (fsCdr FSCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*utils.StoredCdr, error) { +func (fsCdr FSCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*utils.StoredCdr, error) { if utils.IsSliceMember([]string{runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld}, "") { return nil, errors.New(fmt.Sprintf("%s:FieldName", utils.ERR_MANDATORY_IE_MISSING)) // All input field names are mandatory } var err error var hasKey bool - var aTimeStr, durStr string + var sTimeStr, aTimeStr, durStr string rtCdr := new(utils.StoredCdr) rtCdr.MediationRunId = runId rtCdr.Cost = -1.0 // Default for non-rated CDR @@ -223,6 +230,16 @@ func (fsCdr FSCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFl } else if rtCdr.Destination, hasKey = fsCdr[destFld]; !hasKey && fieldsMandatory { return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, destFld)) } + if sTimeStr, hasKey = fsCdr[setupTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(setupTimeFld, utils.STATIC_VALUE_PREFIX) { + return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, setupTimeFld)) + } else { + if strings.HasPrefix(setupTimeFld, utils.STATIC_VALUE_PREFIX) { + sTimeStr = setupTimeFld[1:] + } + if rtCdr.SetupTime, err = utils.ParseTimeDetectLayout(sTimeStr); err != nil && fieldsMandatory { + return nil, err + } + } if aTimeStr, hasKey = fsCdr[answerTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(answerTimeFld, utils.STATIC_VALUE_PREFIX) { return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, answerTimeFld)) } else { diff --git a/cdrs/fscdr_test.go b/cdrs/fscdr_test.go index c7d3dbb2f..605a5f2ec 100644 --- a/cdrs/fscdr_test.go +++ b/cdrs/fscdr_test.go @@ -75,6 +75,11 @@ func TestCDRFields(t *testing.T) { if fsCdr.GetReqType() != utils.RATED { t.Error("Error parsing cdr: ", fsCdr) } + setupTime, _ := fsCdr.GetSetupTime() + expectedSTime, _ := time.Parse(time.RFC3339, "2013-08-04T09:50:54Z") + if setupTime.UTC() != expectedSTime { + t.Error("Error parsing cdr: ", fsCdr) + } answerTime, _ := fsCdr.GetAnswerTime() expectedATime, _ := time.Parse(time.RFC3339, "2013-08-04T09:50:56Z") if answerTime.UTC() != expectedATime { @@ -100,7 +105,7 @@ func TestFsCdrAsStoredCdr(t *testing.T) { if err != nil { t.Errorf("Error loading cdr: %v", err) } - rtCdrOut, err := fsCdr.AsStoredCdr("wholesale_run", "^"+utils.RATED, "^*out", "cgr_tenant", "cgr_tor", "cgr_account", "cgr_subject", "cgr_destination", + rtCdrOut, err := fsCdr.AsStoredCdr("wholesale_run", "^"+utils.RATED, "^*out", "cgr_tenant", "cgr_tor", "cgr_account", "cgr_subject", "cgr_destination", "start_epoch", "answer_epoch", "billsec", []string{"effective_caller_id_number"}, true) if err != nil { t.Error("Unexpected error received", err) @@ -108,25 +113,27 @@ func TestFsCdrAsStoredCdr(t *testing.T) { expctRatedCdr := &utils.StoredCdr{CgrId: utils.FSCgrId("01df56f4-d99a-4ef6-b7fe-b924b2415b7f"), AccId: "01df56f4-d99a-4ef6-b7fe-b924b2415b7f", CdrHost: "127.0.0.1", CdrSource: FS_CDR_SOURCE, ReqType: utils.RATED, Direction: "*out", Tenant: "ipbx.itsyscom.com", TOR: "call", Account: "dan", Subject: "dan", Destination: "+4986517174963", + SetupTime: time.Date(2013, 8, 4, 9, 50, 54, 0, time.UTC).Local(), AnswerTime: time.Date(2013, 8, 4, 9, 50, 56, 0, time.UTC).Local(), Duration: time.Duration(4) * time.Second, ExtraFields: map[string]string{"effective_caller_id_number": "+4986517174960"}, MediationRunId: "wholesale_run", Cost: -1} if !reflect.DeepEqual(rtCdrOut, expctRatedCdr) { t.Errorf("Received: %v, expected: %v", rtCdrOut, expctRatedCdr) } rtCdrOut2, err := fsCdr.AsStoredCdr("wholesale_run", "^postpaid", "^*in", "^cgrates.com", "^premium_call", "^first_account", "^first_subject", "cgr_destination", - "^2013-12-07T08:42:26Z", "^12s", []string{"effective_caller_id_number"}, true) + "^2013-12-07T08:42:24Z", "^2013-12-07T08:42:26Z", "^12s", []string{"effective_caller_id_number"}, true) if err != nil { t.Error("Unexpected error received", err) } expctRatedCdr2 := &utils.StoredCdr{CgrId: utils.FSCgrId("01df56f4-d99a-4ef6-b7fe-b924b2415b7f"), AccId: "01df56f4-d99a-4ef6-b7fe-b924b2415b7f", CdrHost: "127.0.0.1", CdrSource: FS_CDR_SOURCE, ReqType: "postpaid", Direction: "*in", Tenant: "cgrates.com", TOR: "premium_call", Account: "first_account", Subject: "first_subject", Destination: "+4986517174963", + SetupTime: time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2013, 12, 7, 8, 42, 26, 0, time.UTC), Duration: time.Duration(12) * time.Second, ExtraFields: map[string]string{"effective_caller_id_number": "+4986517174960"}, MediationRunId: "wholesale_run", Cost: -1} if !reflect.DeepEqual(rtCdrOut2, expctRatedCdr2) { t.Errorf("Received: %v, expected: %v", rtCdrOut2, expctRatedCdr2) } - _, err = fsCdr.AsStoredCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "answer_time", "duration", []string{"field_extr1", "fieldextr2"}, true) + _, err = fsCdr.AsStoredCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration", []string{"field_extr1", "fieldextr2"}, true) if err == nil { t.Error("Failed to detect missing header") } diff --git a/config/config.go b/config/config.go index ded033340..f035acd99 100644 --- a/config/config.go +++ b/config/config.go @@ -106,6 +106,7 @@ type CGRConfig struct { CdrcAccountField string // Account field identifier. Use index numbers in case of .csv cdrs. CdrcSubjectField string // Subject field identifier. Use index numbers in case of .csv CDRs. CdrcDestinationField string // Destination field identifier. Use index numbers in case of .csv cdrs. + CdrcSetupTimeField string // Setup time field identifier. Use index numbers in case of .csv cdrs. CdrcAnswerTimeField string // Answer time field identifier. Use index numbers in case of .csv cdrs. CdrcDurationField string // Duration field identifier. Use index numbers in case of .csv cdrs. CdrcExtraFields []string // Field identifiers of the fields to add in extra fields section, special format in case of .csv "field1:index1,field2:index2" @@ -115,6 +116,7 @@ type CGRConfig struct { SMRaterReconnects int // Number of reconnect attempts to rater SMDebitInterval int // the period to be debited in advanced during a call (in seconds) SMMaxCallDuration time.Duration // The maximum duration of a call + SMRunIds []string // Identifiers of additional sessions control. SMReqTypeFields []string // Name of request type fields to be used during additional sessions control <""|*default|field_name>. SMDirectionFields []string // Name of direction fields to be used during additional sessions control <""|*default|field_name>. SMTenantFields []string // Name of tenant fields to be used during additional sessions control <""|*default|field_name>. @@ -122,7 +124,8 @@ type CGRConfig struct { SMAccountFields []string // Name of account fields to be used during additional sessions control <""|*default|field_name>. SMSubjectFields []string // Name of fields to be used during additional sessions control <""|*default|field_name>. SMDestFields []string // Name of destination fields to be used during additional sessions control <""|*default|field_name>. - SMAnswerTimeFields []string // Name of time_answer fields to be used during additional sessions control <""|*default|field_name>. + SMSetupTimeFields []string // Name of setup_time fields to be used during additional sessions control <""|*default|field_name>. + SMAnswerTimeFields []string // Name of answer_time fields to be used during additional sessions control <""|*default|field_name>. SMDurationFields []string // Name of duration fields to be used during additional sessions control <""|*default|field_name>. MediatorEnabled bool // Starts Mediator service: . MediatorRater string // Address where to reach the Rater: @@ -135,7 +138,8 @@ type CGRConfig struct { MediatorAccountFields []string // Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs. MediatorSubjectFields []string // Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs. MediatorDestFields []string // Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs. - MediatorAnswerTimeFields []string // Name of time_start fields to be used during mediation. Use index numbers in case of .csv cdrs. + MediatorSetupTimeFields []string // Name of setup_time fields to be used during mediation. Use index numbers in case of .csv cdrs. + MediatorAnswerTimeFields []string // Name of answer_time fields to be used during mediation. Use index numbers in case of .csv cdrs. MediatorDurationFields []string // Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs. FreeswitchServer string // freeswitch address host:port FreeswitchPass string // FS socket password @@ -206,8 +210,9 @@ func (self *CGRConfig) setDefaults() error { self.CdrcAccountField = "5" self.CdrcSubjectField = "6" self.CdrcDestinationField = "7" - self.CdrcAnswerTimeField = "8" - self.CdrcDurationField = "9" + self.CdrcSetupTimeField = "8" + self.CdrcAnswerTimeField = "9" + self.CdrcDurationField = "10" self.CdrcExtraFields = []string{} self.MediatorEnabled = false self.MediatorRater = "internal" @@ -220,6 +225,7 @@ func (self *CGRConfig) setDefaults() error { self.MediatorTORFields = []string{} self.MediatorAccountFields = []string{} self.MediatorDestFields = []string{} + self.MediatorSetupTimeFields = []string{} self.MediatorAnswerTimeFields = []string{} self.MediatorDurationFields = []string{} self.SMEnabled = false @@ -228,6 +234,7 @@ func (self *CGRConfig) setDefaults() error { self.SMRaterReconnects = 3 self.SMDebitInterval = 10 self.SMMaxCallDuration = time.Duration(3) * time.Hour + self.SMRunIds = []string{} self.SMReqTypeFields = []string{} self.SMDirectionFields = []string{} self.SMTenantFields = []string{} @@ -235,6 +242,7 @@ func (self *CGRConfig) setDefaults() error { self.SMAccountFields = []string{} self.SMSubjectFields = []string{} self.SMDestFields = []string{} + self.SMSetupTimeFields = []string{} self.SMAnswerTimeFields = []string{} self.SMDurationFields = []string{} self.FreeswitchServer = "127.0.0.1:8021" @@ -449,6 +457,9 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { if hasOpt = c.HasOption("cdrc", "destination_field"); hasOpt { cfg.CdrcDestinationField, _ = c.GetString("cdrc", "destination_field") } + if hasOpt = c.HasOption("cdrc", "setup_time_field"); hasOpt { + cfg.CdrcSetupTimeField, _ = c.GetString("cdrc", "setup_time_field") + } if hasOpt = c.HasOption("cdrc", "answer_time_field"); hasOpt { cfg.CdrcAnswerTimeField, _ = c.GetString("cdrc", "answer_time_field") } @@ -509,6 +520,11 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { return nil, errParse } } + if hasOpt = c.HasOption("mediator", "setup_time_fields"); hasOpt { + if cfg.MediatorSetupTimeFields, errParse = ConfigSlice(c, "mediator", "setup_time_fields"); errParse != nil { + return nil, errParse + } + } if hasOpt = c.HasOption("mediator", "answer_time_fields"); hasOpt { if cfg.MediatorAnswerTimeFields, errParse = ConfigSlice(c, "mediator", "answer_time_fields"); errParse != nil { return nil, errParse @@ -540,7 +556,11 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { return nil, errParse } } - + if hasOpt = c.HasOption("session_manager", "run_ids"); hasOpt { + if cfg.SMRunIds, errParse = ConfigSlice(c, "session_manager", "run_ids"); errParse != nil { + return nil, errParse + } + } if hasOpt = c.HasOption("session_manager", "reqtype_fields"); hasOpt { if cfg.SMReqTypeFields, errParse = ConfigSlice(c, "session_manager", "reqtype_fields"); errParse != nil { return nil, errParse @@ -576,6 +596,11 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { return nil, errParse } } + if hasOpt = c.HasOption("session_manager", "setup_time_fields"); hasOpt { + if cfg.SMSetupTimeFields, errParse = ConfigSlice(c, "session_manager", "setup_time_fields"); errParse != nil { + return nil, errParse + } + } if hasOpt = c.HasOption("session_manager", "answer_time_fields"); hasOpt { if cfg.SMAnswerTimeFields, errParse = ConfigSlice(c, "session_manager", "answer_time_fields"); errParse != nil { return nil, errParse diff --git a/config/config_test.go b/config/config_test.go index 4bc2bc98a..7107beee8 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -99,8 +99,9 @@ func TestDefaults(t *testing.T) { eCfg.CdrcAccountField = "5" eCfg.CdrcSubjectField = "6" eCfg.CdrcDestinationField = "7" - eCfg.CdrcAnswerTimeField = "8" - eCfg.CdrcDurationField = "9" + eCfg.CdrcSetupTimeField = "8" + eCfg.CdrcAnswerTimeField = "9" + eCfg.CdrcDurationField = "10" eCfg.CdrcExtraFields = []string{} eCfg.MediatorEnabled = false eCfg.MediatorRater = "internal" @@ -113,6 +114,7 @@ func TestDefaults(t *testing.T) { eCfg.MediatorTORFields = []string{} eCfg.MediatorAccountFields = []string{} eCfg.MediatorDestFields = []string{} + eCfg.MediatorSetupTimeFields = []string{} eCfg.MediatorAnswerTimeFields = []string{} eCfg.MediatorDurationFields = []string{} eCfg.SMEnabled = false @@ -121,13 +123,15 @@ func TestDefaults(t *testing.T) { eCfg.SMRaterReconnects = 3 eCfg.SMDebitInterval = 10 eCfg.SMMaxCallDuration = time.Duration(3) * time.Hour + eCfg.SMRunIds = []string{} eCfg.SMReqTypeFields = []string{} - eCfg.SMDirectionFields = []string{} + eCfg.SMDirectionFields = []string{} eCfg.SMTenantFields = []string{} eCfg.SMTORFields = []string{} eCfg.SMAccountFields = []string{} eCfg.SMSubjectFields = []string{} eCfg.SMDestFields = []string{} + eCfg.SMSetupTimeFields = []string{} eCfg.SMAnswerTimeFields = []string{} eCfg.SMDurationFields = []string{} eCfg.FreeswitchServer = "127.0.0.1:8021" @@ -232,6 +236,7 @@ func TestConfigFromFile(t *testing.T) { eCfg.CdrcAccountField = "test" eCfg.CdrcSubjectField = "test" eCfg.CdrcDestinationField = "test" + eCfg.CdrcSetupTimeField = "test" eCfg.CdrcAnswerTimeField = "test" eCfg.CdrcDurationField = "test" eCfg.CdrcExtraFields = []string{"test"} @@ -246,6 +251,7 @@ func TestConfigFromFile(t *testing.T) { eCfg.MediatorTORFields = []string{"test"} eCfg.MediatorAccountFields = []string{"test"} eCfg.MediatorDestFields = []string{"test"} + eCfg.MediatorSetupTimeFields = []string{"test"} eCfg.MediatorAnswerTimeFields = []string{"test"} eCfg.MediatorDurationFields = []string{"test"} eCfg.SMEnabled = true @@ -254,13 +260,15 @@ func TestConfigFromFile(t *testing.T) { eCfg.SMRaterReconnects = 99 eCfg.SMDebitInterval = 99 eCfg.SMMaxCallDuration = time.Duration(99) * time.Second + eCfg.SMRunIds = []string{"test"} eCfg.SMReqTypeFields = []string{"test"} - eCfg.SMDirectionFields = []string{"test"} + eCfg.SMDirectionFields = []string{"test"} eCfg.SMTenantFields = []string{"test"} eCfg.SMTORFields = []string{"test"} eCfg.SMAccountFields = []string{"test"} eCfg.SMSubjectFields = []string{"test"} eCfg.SMDestFields = []string{"test"} + eCfg.SMSetupTimeFields = []string{"test"} eCfg.SMAnswerTimeFields = []string{"test"} eCfg.SMDurationFields = []string{"test"} eCfg.FreeswitchServer = "test" diff --git a/config/test_data.txt b/config/test_data.txt index 8014c7a01..222449d48 100644 --- a/config/test_data.txt +++ b/config/test_data.txt @@ -2,34 +2,34 @@ # [global] -ratingdb_type = test # Rating subsystem database: . -ratingdb_host = test # Rating subsystem database host address. -ratingdb_port = test # Rating subsystem port to reach the database. -ratingdb_name = test # Rating subsystem database name to connect to. -ratingdb_user = test # Rating subsystem username to use when connecting to database. -ratingdb_passwd = test # Rating subsystem password to use when connecting to database. -accountdb_type = test # Accounting subsystem database: . -accountdb_host = test # Accounting subsystem database host address. -accountdb_port = test # Accounting subsystem port to reach the database. -accountdb_name = test # Accounting subsystem database name to connect to. -accountdb_user = test # Accounting subsystem username to use when connecting to database. -accountdb_passwd = test # Accounting subsystem password to use when connecting to database. -stordb_type = test # Log/stored database type to use: -stordb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets. -stordb_port = test # The port to reach the logdb. -stordb_name = test # The name of the log database to connect to. -stordb_user = test # Username to use when connecting to logdb. -stordb_passwd = test # Password to use when connecting to logdb. -dbdata_encoding = test # The encoding used to store object data in strings: -rpc_json_listen = test # RPC JSON listening address -rpc_gob_listen = test # RPC GOB listening address -http_listen = test # HTTP listening address -default_reqtype = test # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>. -default_tor = test # Default Type of Record to consider when missing from requests. -default_tenant = test # Default Tenant to consider when missing from requests. -default_subject = test # Default rating Subject to consider when missing from requests. -rounding_method = test # Rounding method for floats/costs: -rounding_decimals = 99 # Number of decimals to round floats/costs at +ratingdb_type = test # Rating subsystem database: . +ratingdb_host = test # Rating subsystem database host address. +ratingdb_port = test # Rating subsystem port to reach the database. +ratingdb_name = test # Rating subsystem database name to connect to. +ratingdb_user = test # Rating subsystem username to use when connecting to database. +ratingdb_passwd = test # Rating subsystem password to use when connecting to database. +accountdb_type = test # Accounting subsystem database: . +accountdb_host = test # Accounting subsystem database host address. +accountdb_port = test # Accounting subsystem port to reach the database. +accountdb_name = test # Accounting subsystem database name to connect to. +accountdb_user = test # Accounting subsystem username to use when connecting to database. +accountdb_passwd = test # Accounting subsystem password to use when connecting to database. +stordb_type = test # Log/stored database type to use: +stordb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets. +stordb_port = test # The port to reach the logdb. +stordb_name = test # The name of the log database to connect to. +stordb_user = test # Username to use when connecting to logdb. +stordb_passwd = test # Password to use when connecting to logdb. +dbdata_encoding = test # The encoding used to store object data in strings: +rpc_json_listen = test # RPC JSON listening address +rpc_gob_listen = test # RPC GOB listening address +http_listen = test # HTTP listening address +default_reqtype = test # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>. +default_tor = test # Default Type of Record to consider when missing from requests. +default_tenant = test # Default Tenant to consider when missing from requests. +default_subject = test # Default rating Subject to consider when missing from requests. +rounding_method = test # Rounding method for floats/costs: +rounding_decimals = 99 # Number of decimals to round floats/costs at [balancer] @@ -37,7 +37,7 @@ enabled = true # Start Balancer service: . [rater] enabled = true # Enable Rater service: . -balancer = test # Register to Balancer as worker: . +balancer = test # Register to Balancer as worker: . [scheduler] enabled = true # Starts Scheduler service: . @@ -48,9 +48,9 @@ extra_fields = test # Extra fields to store in CDRs mediator = test # Address where to reach the Mediator. Empty for disabling mediation. <""|internal> [cdre] -cdr_format = test # Exported CDRs format -extra_fields = test # List of extra fields to be exported out in CDRs -export_dir = test # Path where the exported CDRs will be placed +cdr_format = test # Exported CDRs format +extra_fields = test # List of extra fields to be exported out in CDRs +export_dir = test # Path where the exported CDRs will be placed [cdrc] enabled = true # Enable CDR client functionality @@ -69,23 +69,25 @@ tor_field = test # Type of Record field identifier. Use index numbers in case account_field = test # Account field identifier. Use index numbers in case of .csv cdrs. subject_field = test # Subject field identifier. Use index numbers in case of .csv CDRs. destination_field = test # Destination field identifier. Use index numbers in case of .csv cdrs. +setup_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs. answer_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs. duration_field = test # Duration field identifier. Use index numbers in case of .csv cdrs. extra_fields = test # Field identifiers of the fields to add in extra fields section, special format in case of .csv "index1:field1,index2:field2" [mediator] enabled = true # Starts Mediator service: . -rater = test # Address where to reach the Rater: -rater_reconnects = 99 # Number of reconnects to rater before giving up. +rater = test # Address where to reach the Rater: +rater_reconnects = 99 # Number of reconnects to rater before giving up. run_ids = test # Identifiers for each mediation run on CDRs subject_fields = test # Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs. -reqtype_fields = test # Name of request type fields to be used during mediation. Use index number in case of .csv cdrs. +reqtype_fields = test # Name of request type fields to be used during mediation. Use index number in case of .csv cdrs. direction_fields = test # Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs. tenant_fields = test # Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs. -tor_fields = test # Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs. +tor_fields = test # Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs. account_fields = test # Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs. destination_fields = test # Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs. -answer_time_fields = test # Name of time_answer fields to be used during mediation. Use index numbers in case of .csv cdrs. +setup_time_fields = test # Name of setup_time fields to be used during mediation. Use index numbers in case of .csv cdrs. +answer_time_fields = test # Name of answer_time fields to be used during mediation. Use index numbers in case of .csv cdrs. duration_fields = test # Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs. [session_manager] @@ -95,6 +97,7 @@ rater = test # Address where to reach the Rater. rater_reconnects = 99 # Number of reconnects to rater before giving up. debit_interval = 99 # Interval to perform debits on. max_call_duration = 99 # Maximum call duration a prepaid call can last +run_ids = test # Identifiers of additional sessions control. reqtype_fields = test # Name of request type fields to be used during additional sessions control <""|*default|field_name>. direction_fields = test # Name of direction fields to be used during additional sessions control <""|*default|field_name>. tenant_fields = test # Name of tenant fields to be used during additional sessions control <""|*default|field_name>. @@ -102,25 +105,26 @@ tor_fields = test # Name of tor fields to be used during additional sessions c account_fields = test # Name of account fields to be used during additional sessions control <""|*default|field_name>. subject_fields = test # Name of fields to be used during additional sessions control <""|*default|field_name>. destination_fields = test # Name of destination fields to be used during additional sessions control <""|*default|field_name>. -answer_time_fields = test # Name of time_answer fields to be used during additional sessions control <""|*default|field_name>. +setup_time_fields = test # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>. +answer_time_fields = test # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>. duration_fields = test # Name of duration fields to be used during additional sessions control <""|*default|field_name>. [freeswitch] -server = test # Adress where to connect to FreeSWITCH socket. +server = test # Adress where to connect to FreeSWITCH socket. passwd = test # FreeSWITCH socket password. reconnects = 99 # Number of attempts on connect failure. [history_server] -enabled = true # Starts History service: . -history_dir = test # Location on disk where to store history files. -save_interval = 99 # Timeout duration between saves +enabled = true # Starts History service: . +history_dir = test # Location on disk where to store history files. +save_interval = 99 # Timeout duration between saves [history_agent] -enabled = true # Starts History as a client: . -server = test # Address where to reach the master history server: +enabled = true # Starts History as a client: . +server = test # Address where to reach the master history server: [mailer] -server = test # The server to use when sending emails out -auth_user = test # Authenticate to email server using this user -auth_passwd = test # Authenticate to email server with this password -from_address = test # From address used when sending emails out +server = test # The server to use when sending emails out +auth_user = test # Authenticate to email server using this user +auth_passwd = test # Authenticate to email server with this password +from_address = test # From address used when sending emails out diff --git a/data/conf/cgrates.cfg b/data/conf/cgrates.cfg index 588edecd9..c41efc38c 100644 --- a/data/conf/cgrates.cfg +++ b/data/conf/cgrates.cfg @@ -71,8 +71,9 @@ # account_field = 5 # Account field identifier. Use index numbers in case of .csv cdrs. # subject_field = 6 # Subject field identifier. Use index numbers in case of .csv CDRs. # destination_field = 7 # Destination field identifier. Use index numbers in case of .csv cdrs. -# answer_time_field = 8 # Answer time field identifier. Use index numbers in case of .csv cdrs. -# duration_field = 9 # Duration field identifier. Use index numbers in case of .csv cdrs. +# setup_time_field = 8 # Setup time field identifier. Use index numbers in case of .csv cdrs. +# answer_time_field = 9 # Answer time field identifier. Use index numbers in case of .csv cdrs. +# duration_field = 10 # Duration field identifier. Use index numbers in case of .csv cdrs. # extra_fields = # Extra fields identifiers. For .csv, format: :[...,:] [mediator] @@ -87,7 +88,8 @@ # account_fields = # Name of account fields to be used during extra mediation. Use index numbers in case of .csv cdrs. # subject_fields = # Name of fields to be used during extra mediation. Use index numbers in case of .csv cdrs. # destination_fields = # Name of destination fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# answer_time_fields = # Name of time_answer fields to be used during extra mediation. Use index numbers in case of .csv cdrs. +# setup_time_fields = # Name of setup_time fields to be used during extra mediation. Use index numbers in case of .csv cdrs. +# answer_time_fields = # Name of answer_time fields to be used during extra mediation. Use index numbers in case of .csv cdrs. # duration_fields = # Name of duration fields to be used during extra mediation. Use index numbers in case of .csv cdrs. [session_manager] @@ -97,6 +99,7 @@ # rater_reconnects = 3 # Number of reconnects to rater before giving up. # debit_interval = 10 # Interval to perform debits on. # max_call_duration = 3h # Maximum call duration a prepaid call can last +# run_ids = # Identifiers of additional sessions control. # reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>. # direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>. # tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>. @@ -104,7 +107,8 @@ # account_fields = # Name of account fields to be used during additional sessions control <""|*default|field_name>. # subject_fields = # Name of fields to be used during additional sessions control <""|*default|field_name>. # destination_fields = # Name of destination fields to be used during additional sessions control <""|*default|field_name>. -# answer_time_fields = # Name of time_answer fields to be used during additional sessions control <""|*default|field_name>. +# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>. +# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>. # duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>. diff --git a/data/conf/samples/apier_local_test.cfg b/data/conf/samples/apier_local_test.cfg index 2cc75d78b..99ccb7e56 100644 --- a/data/conf/samples/apier_local_test.cfg +++ b/data/conf/samples/apier_local_test.cfg @@ -4,127 +4,32 @@ # This is what you get when you load CGRateS with an empty configuration file. # [global] must exist in all files, rest of the configuration is inter-changeable. -[global] -# ratingdb_type = redis # Rating subsystem database: . -# ratingdb_host = 127.0.0.1 # Rating subsystem database host address. -# ratingdb_port = 6379 # Rating subsystem port to reach the database. -# ratingdb_name = 10 # Rating subsystem database name to connect to. -# ratingdb_user = # Rating subsystem username to use when connecting to database. -# ratingdb_passwd = # Rating subsystem password to use when connecting to database. -# accountdb_type = redis # Accounting subsystem database: . -# accountdb_host = 127.0.0.1 # Accounting subsystem database host address. -# accountdb_port = 6379 # Accounting subsystem port to reach the database. -# accountdb_name = 11 # Accounting subsystem database name to connect to. -# accountdb_user = # Accounting subsystem username to use when connecting to database. -# accountdb_passwd = # Accounting subsystem password to use when connecting to database. -# stordb_type = mysql # Stor database type to use: -# stordb_host = 127.0.0.1 # The host to connect to. Values that start with / are for UNIX domain sockets. -# stordb_port = 3306 # The port to reach the logdb. -# stordb_name = cgrates # The name of the log database to connect to. -# stordb_user = cgrates # Username to use when connecting to stordb. -# stordb_passwd = CGRateS.org # Password to use when connecting to stordb. -# dbdata_encoding = msgpack # The encoding used to store object data in strings: -# rpc_json_listen = 127.0.0.1:2012 # RPC JSON listening address -# rpc_gob_listen = 127.0.0.1:2013 # RPC GOB listening address -# http_listen = 127.0.0.1:2080 # HTTP listening address -# default_reqtype = rated # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>. -# default_tor = call # Default Type of Record to consider when missing from requests. -# default_tenant = cgrates.org # Default Tenant to consider when missing from requests. -# default_subject = cgrates # Default rating Subject to consider when missing from requests. -# rounding_method = *middle # Rounding method for floats/costs: <*up|*middle|*down> -# rounding_decimals = 4 # Number of decimals to round float/costs at - -[balancer] -# enabled = false # Start Balancer service: . - [rater] enabled = true # Enable RaterCDRSExportPath service: . -# balancer = # Register to Balancer as worker: <""|internal|127.0.0.1:2013>. [scheduler] enabled = true # Starts Scheduler service: . [cdrs] enabled = true # Start the CDR Server service: . -# extra_fields = # Extra fields to store in CDRs for non-generic CDRs mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal> [cdre] -# cdr_format = csv # Exported CDRs format -# extra_fields = # List of extra fields to be exported out in CDRs export_dir = /tmp/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed [cdrc] -# enabled = false # Enable CDR client functionality -# cdrs = internal # Address where to reach CDR server. -# cdrs_method = http_cgr # Mechanism to use when posting CDRs on server -# run_delay = 0 # Sleep interval in seconds between consecutive runs, 0 to use automation via inotify -# cdr_type = csv # CDR file format . cdr_in_dir = /tmp/cgrates/cdr/cdrc/in # Absolute path towards the directory where the CDRs are stored. cdr_out_dir =/tmp/cgrates/cdr/cdrc/out # Absolute path towards the directory where processed CDRs will be moved. -# cdr_source_id = freeswitch_csv # Free form field, tag identifying the source of the CDRs within CGRS database. -# accid_field = 0 # Accounting id field identifier. Use index number in case of .csv cdrs. -# reqtype_field = 1 # Request type field identifier. Use index number in case of .csv cdrs. -# direction_field = 2 # Direction field identifier. Use index numbers in case of .csv cdrs. -# tenant_field = 3 # Tenant field identifier. Use index numbers in case of .csv cdrs. -# tor_field = 4 # Type of Record field identifier. Use index numbers in case of .csv cdrs. -# account_field = 5 # Account field identifier. Use index numbers in case of .csv cdrs. -# subject_field = 6 # Subject field identifier. Use index numbers in case of .csv CDRs. -# destination_field = 7 # Destination field identifier. Use index numbers in case of .csv cdrs. -# answer_time_field = 8 # Answer time field identifier. Use index numbers in case of .csv cdrs. -# duration_field = 9 # Duration field identifier. Use index numbers in case of .csv cdrs. -# extra_fields = # Extra fields identifiers. For .csv, format: :[...,:] [mediator] enabled = true # Starts Mediator service: . rater = 127.0.0.1:2012 # Address where to reach the Rater: -# rater_reconnects = 3 # Number of reconnects to rater before giving up. -# run_ids = # Identifiers of each extra mediation to run on CDRs -# reqtype_fields = # Name of request type fields to be used during extra mediation. Use index number in case of .csv cdrs. -# direction_fields = # Name of direction fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# tenant_fields = # Name of tenant fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# tor_fields = # Name of tor fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# account_fields = # Name of account fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# subject_fields = # Name of fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# destination_fields = # Name of destination fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# answer_time_fields = # Name of time_answer fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# duration_fields = # Name of duration fields to be used during extra mediation. Use index numbers in case of .csv cdrs. - -[session_manager] -# enabled = false # Starts SessionManager service: . -# switch_type = freeswitch # Defines the type of switch behind: . -# rater = internal # Address where to reach the Rater. -# rater_reconnects = 3 # Number of reconnects to rater before giving up. -# debit_interval = 10 # Interval to perform debits on. -# max_call_duration = 3h # Maximum call duration a prepaid call can last -# reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>. -# direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>. -# tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>. -# tor_fields = # Name of tor fields to be used during additional sessions control <""|*default|field_name>. -# account_fields = # Name of account fields to be used during additional sessions control <""|*default|field_name>. -# subject_fields = # Name of fields to be used during additional sessions control <""|*default|field_name>. -# destination_fields = # Name of destination fields to be used during additional sessions control <""|*default|field_name>. -# answer_time_fields = # Name of time_answer fields to be used during additional sessions control <""|*default|field_name>. -# duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>. - - -[freeswitch] -# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket. -# passwd = ClueCon # FreeSWITCH socket password. -# reconnects = 5 # Number of attempts on connect failure. [history_server] enabled = true # Starts History service: . history_dir = /tmp/cgrates/history # Location on disk where to store history files. -# save_interval = 1s # Interval to save changed cache into .git archive [history_agent] enabled = true # Starts History as a client: . -# server = internal # Address where to reach the master history server: -[mailer] -# server = localhost # The server to use when sending emails out -# auth_user = cgrates # Authenticate to email server using this user -# auth_passwd = CGRateS.org # Authenticate to email server with this password -# from_address = cgr-mailer@localhost.localdomain # From address used when sending emails out diff --git a/data/conf/samples/mediator_test1.cfg b/data/conf/samples/mediator_test1.cfg index aa6c44667..f6d93966d 100644 --- a/data/conf/samples/mediator_test1.cfg +++ b/data/conf/samples/mediator_test1.cfg @@ -3,117 +3,18 @@ # Used in mediator_local_test # Starts rater, cdrs and mediator connecting over internal channel -[global] -# ratingdb_type = redis # Rating subsystem database: . -# ratingdb_host = 127.0.0.1 # Rating subsystem database host address. -# ratingdb_port = 6379 # Rating subsystem port to reach the database. -# ratingdb_name = 10 # Rating subsystem database name to connect to. -# ratingdb_user = # Rating subsystem username to use when connecting to database. -# ratingdb_passwd = # Rating subsystem password to use when connecting to database. -# accountdb_type = redis # Accounting subsystem database: . -# accountdb_host = 127.0.0.1 # Accounting subsystem database host address. -# accountdb_port = 6379 # Accounting subsystem port to reach the database. -# accountdb_name = 11 # Accounting subsystem database name to connect to. -# accountdb_user = # Accounting subsystem username to use when connecting to database. -# accountdb_passwd = # Accounting subsystem password to use when connecting to database. -# stordb_type = mysql # Stor database type to use: -# stordb_host = 127.0.0.1 # The host to connect to. Values that start with / are for UNIX domain sockets. -# stordb_port = 3306 # The port to reach the logdb. -# stordb_name = cgrates # The name of the log database to connect to. -# stordb_user = cgrates # Username to use when connecting to stordb. -# stordb_passwd = CGRateS.org # Password to use when connecting to stordb. -# dbdata_encoding = msgpack # The encoding used to store object data in strings: -# rpc_json_listen = 127.0.0.1:2012 # RPC JSON listening address -# rpc_gob_listen = 127.0.0.1:2013 # RPC GOB listening address -# http_listen = 127.0.0.1:2080 # HTTP listening address -# default_reqtype = rated # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>. -# default_tor = call # Default Type of Record to consider when missing from requests. -# default_tenant = cgrates.org # Default Tenant to consider when missing from requests. -# default_subject = cgrates # Default rating Subject to consider when missing from requests. -# rounding_method = *middle # Rounding method for floats/costs: <*up|*middle|*down> -# rounding_decimals = 4 # Number of decimals to round float/costs at - -[balancer] -# enabled = false # Start Balancer service: . - [rater] enabled = true # Enable RaterCDRSExportPath service: . -# balancer = # Register to Balancer as worker: <""|internal|127.0.0.1:2013>. - -[scheduler] -# enabled = false # Starts Scheduler service: . [cdrs] enabled = true # Start the CDR Server service: . -# extra_fields = # Extra fields to store in CDRs in case of non generic CDRs mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal> [cdre] -# cdr_format = csv # Exported CDRs format -# extra_fields = # List of extra fields to be exported out in CDRs export_dir = /tmp/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed -[cdrc] -# enabled = false # Enable CDR client functionality -# cdrs = internal # Address where to reach CDR server. -# cdrs_method = http_cgr # Mechanism to use when posting CDRs on server -# run_delay = 0 # Sleep interval in seconds between consecutive runs, 0 to use automation via inotify -# cdr_type = csv # CDR file format . -# cdr_in_dir = /var/log/cgrates/cdr/cdrc/in # Absolute path towards the directory where the CDRs are stored. -# cdr_out_dir = /var/log/cgrates/cdr/cdrc/out # Absolute path towards the directory where processed CDRs will be moved. -# cdr_source_id = freeswitch_csv # Free form field, tag identifying the source of the CDRs within CGRS database. -# accid_field = 0 # Accounting id field identifier. Use index number in case of .csv cdrs. -# reqtype_field = 1 # Request type field identifier. Use index number in case of .csv cdrs. -# direction_field = 2 # Direction field identifier. Use index numbers in case of .csv cdrs. -# tenant_field = 3 # Tenant field identifier. Use index numbers in case of .csv cdrs. -# tor_field = 4 # Type of Record field identifier. Use index numbers in case of .csv cdrs. -# account_field = 5 # Account field identifier. Use index numbers in case of .csv cdrs. -# subject_field = 6 # Subject field identifier. Use index numbers in case of .csv CDRs. -# destination_field = 7 # Destination field identifier. Use index numbers in case of .csv cdrs. -# answer_time_field = 8 # Answer time field identifier. Use index numbers in case of .csv cdrs. -# duration_field = 9 # Duration field identifier. Use index numbers in case of .csv cdrs. -# extra_fields = # Extra fields identifiers. For .csv, format: :[...,:] - [mediator] enabled = true # Starts Mediator service: . rater = internal # Address where to reach the Rater: -# rater_reconnects = 3 # Number of reconnects to rater before giving up. -# run_ids = # Identifiers of each extra mediation to run on CDRs -# reqtype_fields = # Name of request type fields to be used during extra mediation. Use index number in case of .csv cdrs. -# direction_fields = # Name of direction fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# tenant_fields = # Name of tenant fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# tor_fields = # Name of tor fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# account_fields = # Name of account fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# subject_fields = # Name of fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# destination_fields = # Name of destination fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# answer_time_fields = # Name of time_answer fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -# duration_fields = # Name of duration fields to be used during extra mediation. Use index numbers in case of .csv cdrs. -[session_manager] -# enabled = false # Starts SessionManager service: . -# switch_type = freeswitch # Defines the type of switch behind: . -# rater = internal # Address where to reach the Rater. -# rater_reconnects = 3 # Number of reconnects to rater before giving up. -# debit_interval = 10 # Interval to perform debits on. -# max_call_duration = 3h # Maximum call duration a prepaid call can last - -[freeswitch] -# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket. -# passwd = ClueCon # FreeSWITCH socket password. -# reconnects = 5 # Number of attempts on connect failure. - -[history_server] -# enabled = false # Starts History service: . -# history_dir = /var/log/cgrates/history # Location on disk where to store history files. -# save_interval = 1s # Interval to save changed cache into .git archive - -[history_agent] -# enabled = false # Starts History as a client: . -# server = internal # Address where to reach the master history server: - -[mailer] -# server = localhost # The server to use when sending emails out -# auth_user = cgrates # Authenticate to email server using this user -# auth_passwd = CGRateS.org # Authenticate to email server with this password -# from_address = cgr-mailer@localhost.localdomain # From address used when sending emails out diff --git a/data/storage/mysql/create_cdrs_tables.sql b/data/storage/mysql/create_cdrs_tables.sql index c7a315051..a37462f4d 100644 --- a/data/storage/mysql/create_cdrs_tables.sql +++ b/data/storage/mysql/create_cdrs_tables.sql @@ -13,6 +13,7 @@ CREATE TABLE cdrs_primary ( account varchar(128) NOT NULL, subject varchar(128) NOT NULL, destination varchar(128) NOT NULL, + setup_time datetime NOT NULL, answer_time datetime NOT NULL, duration bigint NOT NULL, PRIMARY KEY (tbid), diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 80a4c7280..19fffe50c 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -514,8 +514,9 @@ func (self *SQLStorage) LogError(uuid, source, runid, errstr string) (err error) func (self *SQLStorage) SetCdr(cdr utils.RawCDR) (err error) { // map[account:1001 direction:out orig_ip:172.16.1.1 tor:call accid:accid23 answer_time:2013-02-03 19:54:00 cdrsource:freeswitch_csv destination:+4986517174963 duration:62 reqtype:prepaid subject:1001 supplier:supplier1 tenant:cgrates.org] - startTime, _ := cdr.GetAnswerTime() // Ignore errors, we want to store the cdr no matter what - _, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s VALUES (NULL,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s', %d)", + setupTime, _ := cdr.GetSetupTime() // Ignore errors, we want to store the cdr no matter what + answerTime, _ := cdr.GetAnswerTime() // Ignore errors, we want to store the cdr no matter what + _, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s VALUES (NULL,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s', %d)", utils.TBL_CDRS_PRIMARY, cdr.GetCgrId(), cdr.GetAccId(), @@ -528,7 +529,8 @@ func (self *SQLStorage) SetCdr(cdr utils.RawCDR) (err error) { cdr.GetAccount(), cdr.GetSubject(), cdr.GetDestination(), - startTime, + setupTime, + answerTime, cdr.GetDuration(), )) if err != nil { @@ -569,7 +571,7 @@ func (self *SQLStorage) SetRatedCdr(storedCdr *utils.StoredCdr, extraInfo string // ignoreRated - do not consider cdrs which were already rated, including here the ones with errors func (self *SQLStorage) GetStoredCdrs(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,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) + 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 !timeStart.IsZero() { if len(fltr) != 0 { @@ -609,12 +611,12 @@ func (self *SQLStorage) GetStoredCdrs(timeStart, timeEnd time.Time, ignoreErr, i for rows.Next() { var cgrid, accid, cdrhost, cdrsrc, reqtype, direction, tenant, tor, account, subject, destination string var extraFields []byte - var answerTime time.Time + var setupTime, answerTime time.Time var duration int64 var runid sql.NullString // So we can export unmediated CDRs var cost sql.NullFloat64 // So we can export unmediated CDRs var extraFieldsMp map[string]string - if err := rows.Scan(&cgrid, &accid, &cdrhost, &cdrsrc, &reqtype, &direction, &tenant, &tor, &account, &subject, &destination, &answerTime, &duration, + if err := rows.Scan(&cgrid, &accid, &cdrhost, &cdrsrc, &reqtype, &direction, &tenant, &tor, &account, &subject, &destination, &setupTime, &answerTime, &duration, &extraFields, &runid, &cost); err != nil { return nil, err } @@ -623,7 +625,7 @@ func (self *SQLStorage) GetStoredCdrs(timeStart, timeEnd time.Time, ignoreErr, i } storCdr := &utils.StoredCdr{ CgrId: cgrid, AccId: accid, CdrHost: cdrhost, CdrSource: cdrsrc, ReqType: reqtype, Direction: direction, Tenant: tenant, - TOR: tor, Account: account, Subject: subject, Destination: destination, AnswerTime: answerTime, Duration: time.Duration(duration), + TOR: tor, Account: account, Subject: subject, Destination: destination, SetupTime: setupTime, AnswerTime: answerTime, Duration: time.Duration(duration), ExtraFields: extraFieldsMp, MediationRunId: runid.String, Cost: cost.Float64, } cdrs = append(cdrs, storCdr) diff --git a/mediator/mediator.go b/mediator/mediator.go index cc34b6127..b5c3c9591 100644 --- a/mediator/mediator.go +++ b/mediator/mediator.go @@ -52,7 +52,7 @@ type Mediator struct { func (self *Mediator) parseConfig() error { cfgVals := [][]string{self.cgrCfg.MediatorSubjectFields, self.cgrCfg.MediatorReqTypeFields, self.cgrCfg.MediatorDirectionFields, self.cgrCfg.MediatorTenantFields, self.cgrCfg.MediatorTORFields, self.cgrCfg.MediatorAccountFields, self.cgrCfg.MediatorDestFields, - self.cgrCfg.MediatorAnswerTimeFields, self.cgrCfg.MediatorDurationFields} + self.cgrCfg.MediatorSetupTimeFields, self.cgrCfg.MediatorAnswerTimeFields, self.cgrCfg.MediatorDurationFields} // All other configured fields must match the length of reference fields for iCfgVal := range cfgVals { @@ -138,7 +138,8 @@ func (self *Mediator) RateCdr(dbcdr utils.RawCDR) error { for runIdx, runId := range self.cgrCfg.MediatorRunIds { forkedCdr, err := dbcdr.AsStoredCdr(self.cgrCfg.MediatorRunIds[runIdx], self.cgrCfg.MediatorReqTypeFields[runIdx], self.cgrCfg.MediatorDirectionFields[runIdx], self.cgrCfg.MediatorTenantFields[runIdx], self.cgrCfg.MediatorTORFields[runIdx], self.cgrCfg.MediatorAccountFields[runIdx], - self.cgrCfg.MediatorSubjectFields[runIdx], self.cgrCfg.MediatorDestFields[runIdx], self.cgrCfg.MediatorAnswerTimeFields[runIdx], + self.cgrCfg.MediatorSubjectFields[runIdx], self.cgrCfg.MediatorDestFields[runIdx], + self.cgrCfg.MediatorSetupTimeFields[runIdx], self.cgrCfg.MediatorAnswerTimeFields[runIdx], self.cgrCfg.MediatorDurationFields[runIdx], []string{}, true) if err != nil { // Errors on fork, cannot calculate further, write that into db for later analysis self.cdrDb.SetRatedCdr(&utils.StoredCdr{CgrId: dbcdr.GetCgrId(), MediationRunId: runId, Cost: -1.0}, err.Error()) // Cannot fork CDR, important just runid and error diff --git a/sessionmanager/event.go b/sessionmanager/event.go index 336c3d144..a9e222206 100644 --- a/sessionmanager/event.go +++ b/sessionmanager/event.go @@ -34,7 +34,7 @@ type Event interface { GetTOR(string) string GetTenant(string) string GetReqType(string) string - GetStartTime(string) (time.Time, error) + GetAnswerTime(string) (time.Time, error) GetEndTime() (time.Time, error) MissingParameter() bool } diff --git a/sessionmanager/fsevent.go b/sessionmanager/fsevent.go index a7e635a7c..52a70ccdd 100644 --- a/sessionmanager/fsevent.go +++ b/sessionmanager/fsevent.go @@ -42,7 +42,8 @@ const ( CSTMID = "variable_cgr_tenant" CALL_DEST_NR = "Caller-Destination-Number" PARK_TIME = "Caller-Profile-Created-Time" - START_TIME = "Caller-Channel-Answered-Time" + SETUP_TIME = "Caller-Channel-Created-Time" + ANSWER_TIME = "Caller-Channel-Answered-Time" END_TIME = "Caller-Channel-Hangup-Time" NAME = "Event-Name" HEARTBEAT = "HEARTBEAT" @@ -120,7 +121,12 @@ func (fsev FSEvent) MissingParameter() bool { strings.TrimSpace(fsev.GetTenant("")) == "" || strings.TrimSpace(fsev.GetCallDestNr("")) == "" } -func (fsev FSEvent) GetStartTime(field string) (t time.Time, err error) { +func (fsev FSEvent) GetSetupTime(field string) (t time.Time, err error) { + st, err := strconv.ParseInt(fsev[field], 0, 64) + t = time.Unix(0, st*1000) + return +} +func (fsev FSEvent) GetAnswerTime(field string) (t time.Time, err error) { st, err := strconv.ParseInt(fsev[field], 0, 64) t = time.Unix(0, st*1000) return diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 8db75a152..cb66fee38 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -151,7 +151,7 @@ func (sm *FSSessionManager) OnHeartBeat(ev Event) { func (sm *FSSessionManager) OnChannelPark(ev Event) { //engine.Logger.Info("freeswitch park") - startTime, err := ev.GetStartTime(PARK_TIME) + startTime, err := ev.GetAnswerTime(PARK_TIME) if err != nil { engine.Logger.Err("Error parsing answer event start time, using time.Now!") startTime = time.Now() @@ -213,7 +213,7 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { } defer s.Close(ev) // Stop loop and save the costs deducted so far to database if ev.GetReqType("") == utils.POSTPAID { - startTime, err := ev.GetStartTime(START_TIME) + startTime, err := ev.GetAnswerTime(ANSWER_TIME) if err != nil { engine.Logger.Crit("Error parsing postpaid call start time from event") return diff --git a/sessionmanager/session.go b/sessionmanager/session.go index b34689569..902ede552 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -42,7 +42,7 @@ func NewSession(ev Event, sm SessionManager) (s *Session) { if ev.GetReqType("") != utils.PREPAID && ev.GetReqType("") != utils.POSTPAID { return } - startTime, err := ev.GetStartTime(START_TIME) + startTime, err := ev.GetAnswerTime(ANSWER_TIME) if err != nil { engine.Logger.Err("Error parsing answer event start time, using time.Now!") startTime = time.Now() diff --git a/utils/cgrcdr.go b/utils/cgrcdr.go index 544833608..d5147d754 100644 --- a/utils/cgrcdr.go +++ b/utils/cgrcdr.go @@ -95,6 +95,9 @@ func (cgrCdr CgrCdr) GetExtraFields() map[string]string { } return extraFields } +func (cgrCdr CgrCdr) GetSetupTime() (t time.Time, err error) { + return ParseTimeDetectLayout(cgrCdr[SETUP_TIME]) +} func (cgrCdr CgrCdr) GetAnswerTime() (t time.Time, err error) { return ParseTimeDetectLayout(cgrCdr[ANSWER_TIME]) } @@ -107,13 +110,13 @@ func (cgrCdr CgrCdr) GetDuration() time.Duration { // Used in mediation, fieldsMandatory marks whether missing field out of request represents error or can be ignored // If the fields in parameters start with ^ their value is considered instead of dynamically retrieving it from CDR -func (cgrCdr CgrCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) { +func (cgrCdr CgrCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) { if IsSliceMember([]string{runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld}, "") { return nil, errors.New(fmt.Sprintf("%s:FieldName", ERR_MANDATORY_IE_MISSING)) // All input field names are mandatory } var err error var hasKey bool - var aTimeStr, durStr string + var sTimeStr, aTimeStr, durStr string rtCdr := new(StoredCdr) rtCdr.MediationRunId = runId rtCdr.Cost = -1.0 // Default for non-rated CDR @@ -167,6 +170,16 @@ func (cgrCdr CgrCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, tor } else if rtCdr.Destination, hasKey = cgrCdr[destFld]; !hasKey && fieldsMandatory { return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, destFld)) } + if sTimeStr, hasKey = cgrCdr[setupTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(setupTimeFld, STATIC_VALUE_PREFIX) { + return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, setupTimeFld)) + } else { + if strings.HasPrefix(setupTimeFld, STATIC_VALUE_PREFIX) { + sTimeStr = setupTimeFld[1:] + } + if rtCdr.SetupTime, err = ParseTimeDetectLayout(sTimeStr); err != nil && fieldsMandatory { + return nil, err + } + } if aTimeStr, hasKey = cgrCdr[answerTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(answerTimeFld, STATIC_VALUE_PREFIX) { return nil, errors.New(fmt.Sprintf("%s:%s", ERR_MANDATORY_IE_MISSING, answerTimeFld)) } else { diff --git a/utils/cgrcdr_test.go b/utils/cgrcdr_test.go index 6d4410898..5b0ed6bfb 100644 --- a/utils/cgrcdr_test.go +++ b/utils/cgrcdr_test.go @@ -80,30 +80,35 @@ func TestCgrCdrFields(t *testing.T) { func TestCgrCdrAsStoredCdr(t *testing.T) { cgrCdr := &CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "cdrsource": "source_test", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call", - "account": "1001", "subject": "1001", "destination": "1002", "answer_time": "2013-11-07T08:42:26Z", "duration": "10", + "account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:24Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10", "field_extr1": "val_extr1", "fieldextr2": "valextr2"} - rtCdrOut, err := cgrCdr.AsStoredCdr("wholesale_run", "reqtype", "direction", "tenant", "tor", "account", "subject", "destination", "answer_time", "duration", []string{"field_extr1", "fieldextr2"}, true) + rtCdrOut, err := cgrCdr.AsStoredCdr("wholesale_run", "reqtype", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration", + []string{"field_extr1", "fieldextr2"}, true) if err != nil { t.Error("Unexpected error received", err) } expctRatedCdr := &StoredCdr{CgrId: FSCgrId("dsafdsaf"), AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "source_test", ReqType: "rated", - Direction: "*out", Tenant: "cgrates.org", TOR: "call", Account: "1001", Subject: "1001", Destination: "1002", AnswerTime: time.Unix(1383813746, 0).UTC(), + Direction: "*out", Tenant: "cgrates.org", TOR: "call", Account: "1001", Subject: "1001", Destination: "1002", + SetupTime: time.Unix(1383813744, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Duration: 10000000000, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, MediationRunId: "wholesale_run", Cost: -1} if !reflect.DeepEqual(rtCdrOut, expctRatedCdr) { t.Errorf("Received: %v, expected: %v", rtCdrOut, expctRatedCdr) } - rtCdrOut2, err := cgrCdr.AsStoredCdr("wholesale_run", "^postpaid", "^*in", "^cgrates.com", "^premium_call", "^first_account", "^first_subject", "destination", "^2013-12-07T08:42:26Z", "^12s", []string{"field_extr1", "fieldextr2"}, true) + rtCdrOut2, err := cgrCdr.AsStoredCdr("wholesale_run", "^postpaid", "^*in", "^cgrates.com", "^premium_call", "^first_account", "^first_subject", "destination", + "^2013-12-07T08:42:24Z", "^2013-12-07T08:42:26Z", "^12s", []string{"field_extr1", "fieldextr2"}, true) if err != nil { t.Error("Unexpected error received", err) } expctRatedCdr2 := &StoredCdr{CgrId: FSCgrId("dsafdsaf"), AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "source_test", ReqType: "postpaid", Direction: "*in", Tenant: "cgrates.com", TOR: "premium_call", Account: "first_account", Subject: "first_subject", Destination: "1002", + SetupTime: time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2013, 12, 7, 8, 42, 26, 0, time.UTC), Duration: time.Duration(12) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, MediationRunId: "wholesale_run", Cost: -1} if !reflect.DeepEqual(rtCdrOut2, expctRatedCdr2) { t.Errorf("Received: %v, expected: %v", rtCdrOut2, expctRatedCdr2) } - _, err = cgrCdr.AsStoredCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "answer_time", "duration", []string{"field_extr1", "fieldextr2"}, true) + _, err = cgrCdr.AsStoredCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration", + []string{"field_extr1", "fieldextr2"}, true) if err == nil { t.Error("Failed to detect missing header") } diff --git a/utils/consts.go b/utils/consts.go index fd719f817..61b66dd70 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -78,6 +78,7 @@ const ( ACCOUNT = "account" SUBJECT = "subject" DESTINATION = "destination" + SETUP_TIME = "setup_time" ANSWER_TIME = "answer_time" DURATION = "duration" DEFAULT_RUNID = "default" diff --git a/utils/rawcdr.go b/utils/rawcdr.go index a91a4bf77..4577e5e62 100644 --- a/utils/rawcdr.go +++ b/utils/rawcdr.go @@ -37,8 +37,9 @@ type RawCDR interface { GetTOR() string GetTenant() string GetReqType() string - GetAnswerTime() (time.Time, error) + GetSetupTime() (time.Time, error) // Time when the call was set-up + GetAnswerTime() (time.Time, error) // Time when the call was answered GetDuration() time.Duration - GetExtraFields() map[string]string //Stores extra CDR Fields - AsStoredCdr(string, string, string, string, string, string, string, string, string, string, []string, bool) (*StoredCdr, error) // Based on fields queried will return a particular instance of RatedCDR + GetExtraFields() map[string]string //Stores extra CDR Fields + AsStoredCdr(string, string, string, string, string, string, string, string, string, string, string, []string, bool) (*StoredCdr, error) // Based on fields queried will return a particular instance of RatedCDR } diff --git a/utils/storedcdr.go b/utils/storedcdr.go index e7c661482..7c79fe442 100644 --- a/utils/storedcdr.go +++ b/utils/storedcdr.go @@ -38,6 +38,9 @@ func NewStoredCdrFromRawCDR(rawcdr RawCDR) (*StoredCdr, error) { rtCdr.Account = rawcdr.GetAccount() rtCdr.Subject = rawcdr.GetSubject() rtCdr.Destination = rawcdr.GetDestination() + if rtCdr.SetupTime, err = rawcdr.GetSetupTime(); err != nil { + return nil, err + } if rtCdr.AnswerTime, err = rawcdr.GetAnswerTime(); err != nil { return nil, err } @@ -61,6 +64,7 @@ type StoredCdr struct { Account string Subject string Destination string + SetupTime time.Time AnswerTime time.Time Duration time.Duration ExtraFields map[string]string @@ -114,6 +118,10 @@ func (storedCdr *StoredCdr) GetReqType() string { return storedCdr.ReqType } +func (storedCdr *StoredCdr) GetSetupTime() (time.Time, error) { + return storedCdr.SetupTime, nil +} + func (storedCdr *StoredCdr) GetAnswerTime() (time.Time, error) { return storedCdr.AnswerTime, nil } @@ -126,7 +134,7 @@ func (storedCdr *StoredCdr) GetExtraFields() map[string]string { return storedCdr.ExtraFields } -func (storedCdr *StoredCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) { +func (storedCdr *StoredCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) { return storedCdr, nil } @@ -143,6 +151,7 @@ func (storedCdr *StoredCdr) AsRawCdrHttpForm() url.Values { v.Set(ACCOUNT, storedCdr.Account) v.Set(SUBJECT, storedCdr.Subject) v.Set(DESTINATION, storedCdr.Destination) + v.Set(SETUP_TIME, storedCdr.SetupTime.String()) v.Set(ANSWER_TIME, storedCdr.AnswerTime.String()) v.Set(DURATION, strconv.FormatFloat(storedCdr.Duration.Seconds(), 'f', -1, 64)) for fld, val := range storedCdr.ExtraFields {