From 9b37e97cc29866625f5a1a9864357f2145aa8df1 Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 4 Jul 2014 20:05:27 +0200 Subject: [PATCH] RunFilter in DerivedChargers, ParseEventValue accepting RSRField in FreeSWITCH event --- config/config_test.go | 14 +- config/helpers.go | 30 +-- config/helpers_test.go | 10 + config/test_data.txt | 1 + data/conf/cgrates.cfg | 1 + mediator/mediator.go | 5 + sessionmanager/event.go | 2 + sessionmanager/fsevent.go | 46 ++++ sessionmanager/fsevent_test.go | 325 +++++++++++++++++------------ sessionmanager/fssessionmanager.go | 4 + utils/derivedchargers.go | 36 ++-- utils/derivedchargers_test.go | 10 +- 12 files changed, 314 insertions(+), 170 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index 03c1aece4..dc90fd857 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -250,7 +250,7 @@ func TestConfigFromFile(t *testing.T) { eCfg.FreeswitchServer = "test" eCfg.FreeswitchPass = "test" eCfg.FreeswitchReconnects = 99 - eCfg.DerivedChargers = utils.DerivedChargers{&utils.DerivedCharger{RunId: "test", ReqTypeField: "test", DirectionField: "test", TenantField: "test", + eCfg.DerivedChargers = utils.DerivedChargers{&utils.DerivedCharger{RunId: "test", RunFilter: "", ReqTypeField: "test", DirectionField: "test", TenantField: "test", CategoryField: "test", AccountField: "test", SubjectField: "test", DestinationField: "test", SetupTimeField: "test", AnswerTimeField: "test", UsageField: "test"}} eCfg.CombinedDerivedChargers = true eCfg.HistoryAgentEnabled = true @@ -264,7 +264,13 @@ func TestConfigFromFile(t *testing.T) { eCfg.MailerFromAddr = "test" if !reflect.DeepEqual(cfg, eCfg) { t.Log(eCfg) + for _, eDC := range eCfg.DerivedChargers { + fmt.Printf("ExpectDerivedChargers: %+v\n", eDC) + } t.Log(cfg) + for _, eDC := range cfg.DerivedChargers { + fmt.Printf("DerivedChargers: %+v\n", eDC) + } t.Error("Loading of configuration from file failed!") } } @@ -288,12 +294,6 @@ extra_fields = ~effective_caller_id_number:s/(\d+)/+$1/ t.Errorf("Unexpected value for config CdrsExtraFields: %v", cfg.CDRSExtraFields) } eFieldsCfg = []byte(`[cdrs] -extra_fields = extr1,extr2, -`) - if _, err := NewCGRConfigFromBytes(eFieldsCfg); err == nil { - t.Error("Failed to detect empty field in the end of extra fields defition") - } - eFieldsCfg = []byte(`[cdrs] extra_fields = extr1,~extr2:s/x.+/ `) if _, err := NewCGRConfigFromBytes(eFieldsCfg); err == nil { diff --git a/config/helpers.go b/config/helpers.go index 580ebb7f6..02fad02ce 100644 --- a/config/helpers.go +++ b/config/helpers.go @@ -29,15 +29,11 @@ import ( // Adds support for slice values in config func ConfigSlice(cfgVal string) ([]string, error) { - cfgValStrs := strings.Split(cfgVal, ",") // If need arrises, we can make the separator configurable - if len(cfgValStrs) == 1 && cfgValStrs[0] == "" { // Prevents returning iterable with empty value - return []string{}, nil - } + cfgValStrs := strings.Split(cfgVal, ",") // If need arrises, we can make the separator configurable for idx, elm := range cfgValStrs { - if elm == "" { //One empty element is presented when splitting empty string - return nil, errors.New("Empty values in config slice") - - } + //if elm == "" { //One empty element is presented when splitting empty string + // return nil, errors.New("Empty values in config slice") + //} cfgValStrs[idx] = strings.TrimSpace(elm) // By default spaces are not removed so we do it here to avoid unpredicted results in config } return cfgValStrs, nil @@ -50,10 +46,6 @@ func ParseRSRFields(configVal string) ([]*utils.RSRField, error) { } rsrFields := make([]*utils.RSRField, len(cfgValStrs)) for idx, cfgValStr := range cfgValStrs { - if len(cfgValStr) == 0 { //One empty element is presented when splitting empty string - return nil, errors.New("Empty values in config slice") - - } if rsrField, err := utils.NewRSRField(cfgValStr); err != nil { return nil, err } else { @@ -65,11 +57,15 @@ func ParseRSRFields(configVal string) ([]*utils.RSRField, error) { // Parse the configuration file and returns utils.DerivedChargers instance if no errors func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err error) { - var runIds, reqTypeFlds, directionFlds, tenantFlds, torFlds, acntFlds, subjFlds, dstFlds, sTimeFlds, aTimeFlds, durFlds []string + var runIds, runFilters, reqTypeFlds, directionFlds, tenantFlds, torFlds, acntFlds, subjFlds, dstFlds, sTimeFlds, aTimeFlds, durFlds []string cfgVal, _ := c.GetString("derived_charging", "run_ids") if runIds, err = ConfigSlice(cfgVal); err != nil { return nil, err } + cfgVal, _ = c.GetString("derived_charging", "run_filters") + if runFilters, err = ConfigSlice(cfgVal); err != nil { + return nil, err + } cfgVal, _ = c.GetString("derived_charging", "reqtype_fields") if reqTypeFlds, err = ConfigSlice(cfgVal); err != nil { return nil, err @@ -111,7 +107,8 @@ func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err return nil, err } // We need all to be the same length - if len(reqTypeFlds) != len(runIds) || + if len(runFilters) != len(runIds) || + len(reqTypeFlds) != len(runIds) || len(directionFlds) != len(runIds) || len(tenantFlds) != len(runIds) || len(torFlds) != len(runIds) || @@ -125,8 +122,11 @@ func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err } // Create the individual chargers and append them to the final instance dcs = make(utils.DerivedChargers, 0) + if len(runIds) == 1 && len(runIds[0]) == 0 { // Avoid iterating on empty runid + return dcs, nil + } for runIdx, runId := range runIds { - dc, err := utils.NewDerivedCharger(runId, reqTypeFlds[runIdx], directionFlds[runIdx], tenantFlds[runIdx], torFlds[runIdx], + dc, err := utils.NewDerivedCharger(runId, runFilters[runIdx], reqTypeFlds[runIdx], directionFlds[runIdx], tenantFlds[runIdx], torFlds[runIdx], acntFlds[runIdx], subjFlds[runIdx], dstFlds[runIdx], sTimeFlds[runIdx], aTimeFlds[runIdx], durFlds[runIdx]) if err != nil { return nil, err diff --git a/config/helpers_test.go b/config/helpers_test.go index 741a7065c..13aaeff36 100644 --- a/config/helpers_test.go +++ b/config/helpers_test.go @@ -26,6 +26,15 @@ import ( "github.com/cgrates/cgrates/utils" ) +func TestConfigSlice(t *testing.T) { + eCS := []string{"", ""} + if cs, err := ConfigSlice(" , "); err != nil { + t.Error("Unexpected error: ", err) + } else if !reflect.DeepEqual(eCS, cs) { + t.Errorf("Expecting: %v, received: %v", eCS, cs) + } +} + func TestParseRSRFields(t *testing.T) { fields := `host,~sip_redirected_to:s/sip:\+49(\d+)@/0$1/,destination` expectParsedFields := []*utils.RSRField{&utils.RSRField{Id: "host"}, @@ -41,6 +50,7 @@ func TestParseRSRFields(t *testing.T) { func TestParseCfgDerivedCharging(t *testing.T) { eFieldsCfg := []byte(`[derived_charging] run_ids = run1, run2 +run_filters =, reqtype_fields = test1, test2 direction_fields = test1, test2 tenant_fields = test1, test2 diff --git a/config/test_data.txt b/config/test_data.txt index bd9e050d9..77104a741 100644 --- a/config/test_data.txt +++ b/config/test_data.txt @@ -100,6 +100,7 @@ reconnects = 99 # Number of attempts on connect failure. [derived_charging] run_ids = test # Identifiers of additional sessions control. +run_filters = # No filters applied 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>. diff --git a/data/conf/cgrates.cfg b/data/conf/cgrates.cfg index 70da87d56..2f336a50d 100644 --- a/data/conf/cgrates.cfg +++ b/data/conf/cgrates.cfg @@ -104,6 +104,7 @@ [derived_charging] # run_ids = # Identifiers of additional sessions control. +# run_filters = # List of cdr field filters for each run. # 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>. diff --git a/mediator/mediator.go b/mediator/mediator.go index 09997e17b..6dc813935 100644 --- a/mediator/mediator.go +++ b/mediator/mediator.go @@ -120,6 +120,11 @@ func (self *Mediator) RateCdr(storedCdr *utils.StoredCdr) error { return errors.New(errText) } for _, dc := range dcs { + dcRunFilter, _ := utils.NewRSRField(dc.RunFilter) + if dcRunFilter != nil && storedCdr.FieldAsString(&utils.RSRField{Id: dcRunFilter.Id}) != storedCdr.FieldAsString(dcRunFilter) { + engine.Logger.Info(fmt.Sprintf("Ignoring DerivedCharger with id %s due to non matching filter", dc.RunId)) + continue + } dcReqTypeFld, _ := utils.NewRSRField(dc.ReqTypeField) dcDirFld, _ := utils.NewRSRField(dc.DirectionField) dcTenantFld, _ := utils.NewRSRField(dc.TenantField) diff --git a/sessionmanager/event.go b/sessionmanager/event.go index c39c3a8b8..4b4f1c4e3 100644 --- a/sessionmanager/event.go +++ b/sessionmanager/event.go @@ -19,6 +19,7 @@ along with this program. If not, see package sessionmanager import ( + "github.com/cgrates/cgrates/utils" "time" ) @@ -40,4 +41,5 @@ type Event interface { GetEndTime() (time.Time, error) GetDuration(string) (time.Duration, error) MissingParameter() bool + ParseEventValue(*utils.RSRField) string } diff --git a/sessionmanager/fsevent.go b/sessionmanager/fsevent.go index 0d54d42b6..3f75d983b 100644 --- a/sessionmanager/fsevent.go +++ b/sessionmanager/fsevent.go @@ -20,6 +20,7 @@ package sessionmanager import ( "fmt" + "strconv" "strings" "time" @@ -203,3 +204,48 @@ func (fsev FSEvent) GetDuration(fieldName string) (dur time.Duration, err error) } return utils.ParseDurationWithSecs(durStr) } + +// Used in derived charging and sittuations when we need to run regexp on fields +func (fsev FSEvent) ParseEventValue(rsrFld *utils.RSRField) string { + switch rsrFld.Id { + case utils.CGRID: + return rsrFld.ParseValue(fsev.GetCgrId()) + case utils.TOR: + return rsrFld.ParseValue(utils.VOICE) + case utils.ACCID: + return rsrFld.ParseValue(fsev.GetUUID()) + case utils.CDRHOST: + return rsrFld.ParseValue(fsev["FreeSWITCH-IPv4"]) + case utils.CDRSOURCE: + return rsrFld.ParseValue("FS_EVENT") + case utils.REQTYPE: + return rsrFld.ParseValue(fsev.GetReqType("")) + case utils.DIRECTION: + return rsrFld.ParseValue(fsev.GetDirection("")) + case utils.TENANT: + return rsrFld.ParseValue(fsev.GetTenant("")) + case utils.CATEGORY: + return rsrFld.ParseValue(fsev.GetCategory("")) + case utils.ACCOUNT: + return rsrFld.ParseValue(fsev.GetAccount("")) + case utils.SUBJECT: + return rsrFld.ParseValue(fsev.GetSubject("")) + case utils.DESTINATION: + return rsrFld.ParseValue(fsev.GetDestination("")) + case utils.SETUP_TIME: + st, _ := fsev.GetSetupTime("") + return rsrFld.ParseValue(st.String()) + case utils.ANSWER_TIME: + at, _ := fsev.GetAnswerTime("") + return rsrFld.ParseValue(at.String()) + case utils.USAGE: + dur, _ := fsev.GetDuration("") + return rsrFld.ParseValue(strconv.FormatInt(dur.Nanoseconds(), 10)) + case utils.MEDI_RUNID: + return rsrFld.ParseValue(utils.DEFAULT_RUNID) + case utils.COST: + return rsrFld.ParseValue(strconv.FormatFloat(-1, 'f', -1, 64)) // Recommended to use FormatCost + default: + return rsrFld.ParseValue(fsev[rsrFld.Id]) + } +} diff --git a/sessionmanager/fsevent_test.go b/sessionmanager/fsevent_test.go index 961b7aeef..04ceab740 100644 --- a/sessionmanager/fsevent_test.go +++ b/sessionmanager/fsevent_test.go @@ -26,138 +26,7 @@ import ( "github.com/cgrates/cgrates/utils" ) -func TestEventCreation(t *testing.T) { - body := `Event-Name: RE_SCHEDULE -Core-UUID: 792e181c-b6e6-499c-82a1-52a778e7d82d -FreeSWITCH-Hostname: h1.ip-switch.net -FreeSWITCH-Switchname: h1.ip-switch.net -FreeSWITCH-IPv4: 88.198.12.156 -FreeSWITCH-IPv6: %3A%3A1 -Event-Date-Local: 2012-10-05%2013%3A41%3A38 -Event-Date-GMT: Fri,%2005%20Oct%202012%2011%3A41%3A38%20GMT -Event-Date-Timestamp: 1349437298012866 -Event-Calling-File: switch_scheduler.c -Event-Calling-Function: switch_scheduler_execute -Event-Calling-Line-Number: 65 -Event-Sequence: 34263 -Task-ID: 2 -Task-Desc: heartbeat -Task-Group: core -Task-Runtime: 1349437318` - ev := new(FSEvent).New(body) - if ev.GetName() != "RE_SCHEDULE" { - t.Error("Event not parsed correctly: ", ev) - } - l := len(ev.(FSEvent)) - if l != 17 { - t.Error("Incorrect number of event fields: ", l) - } -} - -// Detects if any of the parsers do not return static values -func TestEventParseStatic(t *testing.T) { - ev := new(FSEvent).New("") - setupTime, _ := ev.GetSetupTime("^2013-12-07 08:42:24") - answerTime, _ := ev.GetAnswerTime("^2013-12-07 08:42:24") - dur, _ := ev.GetDuration("^60s") - if ev.GetReqType("^test") != "test" || - ev.GetDirection("^test") != "test" || - ev.GetTenant("^test") != "test" || - ev.GetCategory("^test") != "test" || - ev.GetAccount("^test") != "test" || - ev.GetSubject("^test") != "test" || - ev.GetDestination("^test") != "test" || - setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || - answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || - dur != time.Duration(60)*time.Second { - t.Error("Values out of static not matching", - ev.GetReqType("^test") != "test", - ev.GetDirection("^test") != "test", - ev.GetTenant("^test") != "test", - ev.GetCategory("^test") != "test", - ev.GetAccount("^test") != "test", - ev.GetSubject("^test") != "test", - ev.GetDestination("^test") != "test", - setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), - answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), - dur != time.Duration(60)*time.Second) - } -} - -// Test here if the answer is selected out of headers we specify, even if not default defined -func TestEventSelectiveHeaders(t *testing.T) { - body := `Event-Name: RE_SCHEDULE -Core-UUID: 792e181c-b6e6-499c-82a1-52a778e7d82d -FreeSWITCH-Hostname: h1.ip-switch.net -FreeSWITCH-Switchname: h1.ip-switch.net -FreeSWITCH-IPv4: 88.198.12.156 -FreeSWITCH-IPv6: %3A%3A1 -Event-Date-Local: 2012-10-05%2013%3A41%3A38 -Event-Date-GMT: Fri,%2005%20Oct%202012%2011%3A41%3A38%20GMT -Event-Date-Timestamp: 1349437298012866 -Event-Calling-File: switch_scheduler.c -Event-Calling-Function: switch_scheduler_execute -Event-Calling-Line-Number: 65 -Event-Sequence: 34263 -Task-ID: 2 -Task-Desc: heartbeat -Task-Group: core -Task-Runtime: 1349437318` - cfg, _ = config.NewDefaultCGRConfig() - config.SetCgrConfig(cfg) - ev := new(FSEvent).New(body) - setupTime, _ := ev.GetSetupTime("Event-Date-Local") - answerTime, _ := ev.GetAnswerTime("Event-Date-Local") - dur, _ := ev.GetDuration("Event-Calling-Line-Number") - if ev.GetReqType("FreeSWITCH-Hostname") != "h1.ip-switch.net" || - ev.GetDirection("FreeSWITCH-Hostname") != "*out" || - ev.GetTenant("FreeSWITCH-Hostname") != "h1.ip-switch.net" || - ev.GetCategory("FreeSWITCH-Hostname") != "h1.ip-switch.net" || - ev.GetAccount("FreeSWITCH-Hostname") != "h1.ip-switch.net" || - ev.GetSubject("FreeSWITCH-Hostname") != "h1.ip-switch.net" || - ev.GetDestination("FreeSWITCH-Hostname") != "h1.ip-switch.net" || - setupTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC) || - answerTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC) || - dur != time.Duration(65)*time.Second { - t.Error("Values out of static not matching", - ev.GetReqType("FreeSWITCH-Hostname") != "h1.ip-switch.net", - ev.GetDirection("FreeSWITCH-Hostname") != "*out", - ev.GetTenant("FreeSWITCH-Hostname") != "h1.ip-switch.net", - ev.GetCategory("FreeSWITCH-Hostname") != "h1.ip-switch.net", - ev.GetAccount("FreeSWITCH-Hostname") != "h1.ip-switch.net", - ev.GetSubject("FreeSWITCH-Hostname") != "h1.ip-switch.net", - ev.GetDestination("FreeSWITCH-Hostname") != "h1.ip-switch.net", - setupTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC), - answerTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC), - dur != time.Duration(65)*time.Second) - } -} - -func TestDDazEmptyTime(t *testing.T) { - body := `Event-Name: RE_SCHEDULE -Core-UUID: 792e181c-b6e6-499c-82a1-52a778e7d82d -FreeSWITCH-Hostname: h1.ip-switch.net -FreeSWITCH-Switchname: h1.ip-switch.net -FreeSWITCH-IPv4: 88.198.12.156 -Caller-Channel-Created-Time: 0 -Caller-Channel-Answered-Time -Task-Runtime: 1349437318` - var nilTime time.Time - ev := new(FSEvent).New(body) - if setupTime, err := ev.GetSetupTime(""); err != nil { - t.Error("Error when parsing empty setupTime") - } else if setupTime != nilTime { - t.Error("Expecting nil time, got: ", setupTime) - } - if answerTime, err := ev.GetAnswerTime(""); err != nil { - t.Error("Error when parsing empty setupTime") - } else if answerTime != nilTime { - t.Error("Expecting nil time, got: ", answerTime) - } -} - -func TestParseFsHangup(t *testing.T) { - hangupEv := `Event-Name: CHANNEL_HANGUP_COMPLETE +var hangupEv string = `Event-Name: CHANNEL_HANGUP_COMPLETE Core-UUID: bb890f9e-0aae-476d-8292-91b434eb4f73 FreeSWITCH-Hostname: iPBXDev FreeSWITCH-Switchname: iPBXDev @@ -469,6 +338,138 @@ variable_rtp_audio_out_cng_packet_count: 0 variable_rtp_audio_rtcp_packet_count: 0 variable_rtp_audio_rtcp_octet_count: 0 ` + +func TestEventCreation(t *testing.T) { + body := `Event-Name: RE_SCHEDULE +Core-UUID: 792e181c-b6e6-499c-82a1-52a778e7d82d +FreeSWITCH-Hostname: h1.ip-switch.net +FreeSWITCH-Switchname: h1.ip-switch.net +FreeSWITCH-IPv4: 88.198.12.156 +FreeSWITCH-IPv6: %3A%3A1 +Event-Date-Local: 2012-10-05%2013%3A41%3A38 +Event-Date-GMT: Fri,%2005%20Oct%202012%2011%3A41%3A38%20GMT +Event-Date-Timestamp: 1349437298012866 +Event-Calling-File: switch_scheduler.c +Event-Calling-Function: switch_scheduler_execute +Event-Calling-Line-Number: 65 +Event-Sequence: 34263 +Task-ID: 2 +Task-Desc: heartbeat +Task-Group: core +Task-Runtime: 1349437318` + ev := new(FSEvent).New(body) + if ev.GetName() != "RE_SCHEDULE" { + t.Error("Event not parsed correctly: ", ev) + } + l := len(ev.(FSEvent)) + if l != 17 { + t.Error("Incorrect number of event fields: ", l) + } +} + +// Detects if any of the parsers do not return static values +func TestEventParseStatic(t *testing.T) { + ev := new(FSEvent).New("") + setupTime, _ := ev.GetSetupTime("^2013-12-07 08:42:24") + answerTime, _ := ev.GetAnswerTime("^2013-12-07 08:42:24") + dur, _ := ev.GetDuration("^60s") + if ev.GetReqType("^test") != "test" || + ev.GetDirection("^test") != "test" || + ev.GetTenant("^test") != "test" || + ev.GetCategory("^test") != "test" || + ev.GetAccount("^test") != "test" || + ev.GetSubject("^test") != "test" || + ev.GetDestination("^test") != "test" || + setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || + answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || + dur != time.Duration(60)*time.Second { + t.Error("Values out of static not matching", + ev.GetReqType("^test") != "test", + ev.GetDirection("^test") != "test", + ev.GetTenant("^test") != "test", + ev.GetCategory("^test") != "test", + ev.GetAccount("^test") != "test", + ev.GetSubject("^test") != "test", + ev.GetDestination("^test") != "test", + setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), + answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), + dur != time.Duration(60)*time.Second) + } +} + +// Test here if the answer is selected out of headers we specify, even if not default defined +func TestEventSelectiveHeaders(t *testing.T) { + body := `Event-Name: RE_SCHEDULE +Core-UUID: 792e181c-b6e6-499c-82a1-52a778e7d82d +FreeSWITCH-Hostname: h1.ip-switch.net +FreeSWITCH-Switchname: h1.ip-switch.net +FreeSWITCH-IPv4: 88.198.12.156 +FreeSWITCH-IPv6: %3A%3A1 +Event-Date-Local: 2012-10-05%2013%3A41%3A38 +Event-Date-GMT: Fri,%2005%20Oct%202012%2011%3A41%3A38%20GMT +Event-Date-Timestamp: 1349437298012866 +Event-Calling-File: switch_scheduler.c +Event-Calling-Function: switch_scheduler_execute +Event-Calling-Line-Number: 65 +Event-Sequence: 34263 +Task-ID: 2 +Task-Desc: heartbeat +Task-Group: core +Task-Runtime: 1349437318` + cfg, _ = config.NewDefaultCGRConfig() + config.SetCgrConfig(cfg) + ev := new(FSEvent).New(body) + setupTime, _ := ev.GetSetupTime("Event-Date-Local") + answerTime, _ := ev.GetAnswerTime("Event-Date-Local") + dur, _ := ev.GetDuration("Event-Calling-Line-Number") + if ev.GetReqType("FreeSWITCH-Hostname") != "h1.ip-switch.net" || + ev.GetDirection("FreeSWITCH-Hostname") != "*out" || + ev.GetTenant("FreeSWITCH-Hostname") != "h1.ip-switch.net" || + ev.GetCategory("FreeSWITCH-Hostname") != "h1.ip-switch.net" || + ev.GetAccount("FreeSWITCH-Hostname") != "h1.ip-switch.net" || + ev.GetSubject("FreeSWITCH-Hostname") != "h1.ip-switch.net" || + ev.GetDestination("FreeSWITCH-Hostname") != "h1.ip-switch.net" || + setupTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC) || + answerTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC) || + dur != time.Duration(65)*time.Second { + t.Error("Values out of static not matching", + ev.GetReqType("FreeSWITCH-Hostname") != "h1.ip-switch.net", + ev.GetDirection("FreeSWITCH-Hostname") != "*out", + ev.GetTenant("FreeSWITCH-Hostname") != "h1.ip-switch.net", + ev.GetCategory("FreeSWITCH-Hostname") != "h1.ip-switch.net", + ev.GetAccount("FreeSWITCH-Hostname") != "h1.ip-switch.net", + ev.GetSubject("FreeSWITCH-Hostname") != "h1.ip-switch.net", + ev.GetDestination("FreeSWITCH-Hostname") != "h1.ip-switch.net", + setupTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC), + answerTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC), + dur != time.Duration(65)*time.Second) + } +} + +func TestDDazEmptyTime(t *testing.T) { + body := `Event-Name: RE_SCHEDULE +Core-UUID: 792e181c-b6e6-499c-82a1-52a778e7d82d +FreeSWITCH-Hostname: h1.ip-switch.net +FreeSWITCH-Switchname: h1.ip-switch.net +FreeSWITCH-IPv4: 88.198.12.156 +Caller-Channel-Created-Time: 0 +Caller-Channel-Answered-Time +Task-Runtime: 1349437318` + var nilTime time.Time + ev := new(FSEvent).New(body) + if setupTime, err := ev.GetSetupTime(""); err != nil { + t.Error("Error when parsing empty setupTime") + } else if setupTime != nilTime { + t.Error("Expecting nil time, got: ", setupTime) + } + if answerTime, err := ev.GetAnswerTime(""); err != nil { + t.Error("Error when parsing empty setupTime") + } else if answerTime != nilTime { + t.Error("Expecting nil time, got: ", answerTime) + } +} + +func TestParseFsHangup(t *testing.T) { cfg, _ = config.NewDefaultCGRConfig() config.SetCgrConfig(cfg) ev := new(FSEvent).New(hangupEv) @@ -498,3 +499,63 @@ variable_rtp_audio_rtcp_octet_count: 0 dur != time.Duration(5)*time.Second) } } + +func TestParseEventValue(t *testing.T) { + cfg, _ = config.NewDefaultCGRConfig() + config.SetCgrConfig(cfg) + ev := new(FSEvent).New(hangupEv) + if cgrid := ev.ParseEventValue(&utils.RSRField{Id: utils.CGRID}); cgrid != "8b1ca78a9bbaa42c811e60b974188197c425dbe7" { + t.Error("Unexpected cgrid parsed", cgrid) + } + if tor := ev.ParseEventValue(&utils.RSRField{Id: utils.TOR}); tor != utils.VOICE { + t.Error("Unexpected tor parsed", tor) + } + if accid := ev.ParseEventValue(&utils.RSRField{Id: utils.ACCID}); accid != "37e9b766-5256-4e4b-b1ed-3767b930fec8" { + t.Error("Unexpected result parsed", accid) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.CDRHOST}); parsed != "10.0.2.15" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.CDRSOURCE}); parsed != "FS_EVENT" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.REQTYPE}); parsed != utils.PSEUDOPREPAID { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.DIRECTION}); parsed != utils.OUT { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.TENANT}); parsed != "cgrates.org" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.CATEGORY}); parsed != "call" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.ACCOUNT}); parsed != "1003" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.SUBJECT}); parsed != "1003" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.DESTINATION}); parsed != "1002" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.SETUP_TIME}); parsed != "2014-04-25 18:08:27 +0200 CEST" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.ANSWER_TIME}); parsed != "2014-04-25 18:08:40 +0200 CEST" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.USAGE}); parsed != "5000000000" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.MEDI_RUNID}); parsed != utils.DEFAULT_RUNID { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: utils.COST}); parsed != "-1" { + t.Error("Unexpected result parsed", parsed) + } + if parsed := ev.ParseEventValue(&utils.RSRField{Id: "Hangup-Cause"}); parsed != "NORMAL_CLEARING" { + t.Error("Unexpected result parsed", parsed) + } +} diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index bbe46e0d5..5a5c8685b 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -161,6 +161,10 @@ func (sm *FSSessionManager) OnChannelPark(ev Event) { } dcs, _ = dcs.AppendDefaultRun() for _, dc := range dcs { + dcRunFilter, _ := utils.NewRSRField(dc.RunFilter) + if dcRunFilter != nil && ev.ParseEventValue(&utils.RSRField{Id: dcRunFilter.Id}) != ev.ParseEventValue(dcRunFilter) { + engine.Logger.Info(fmt.Sprintf("Ignoring DerivedCharger with id %s due to non matching filter", dc.RunId)) + } startTime, err := ev.GetAnswerTime(PARK_TIME) if err != nil { engine.Logger.Err("Error parsing answer event start time, using time.Now!") diff --git a/utils/derivedchargers.go b/utils/derivedchargers.go index 2795ddf40..296d05d15 100644 --- a/utils/derivedchargers.go +++ b/utils/derivedchargers.go @@ -24,67 +24,75 @@ import ( ) // Wraps regexp compiling in case of rsr fields -func NewDerivedCharger(runId, reqTypeFld, dirFld, tenantFld, catFld, acntFld, subjFld, dstFld, sTimeFld, aTimeFld, durFld string) (dc *DerivedCharger, err error) { +func NewDerivedCharger(runId, runFilter, reqTypeFld, dirFld, tenantFld, catFld, acntFld, subjFld, dstFld, sTimeFld, aTimeFld, durFld string) (dc *DerivedCharger, err error) { if len(runId) == 0 { return nil, errors.New("Empty run id field") } dc = &DerivedCharger{RunId: runId} + dc.RunFilter = runFilter + if strings.HasPrefix(dc.RunFilter, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { + if dc.rsrRunFilter, err = NewRSRField(dc.RunFilter); err != nil { + return nil, err + } else if len(dc.rsrRunFilter.Id) == 0 { + return nil, errors.New("Empty filter header.") + } + } dc.ReqTypeField = reqTypeFld - if strings.HasPrefix(dc.ReqTypeField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.ReqTypeField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrReqTypeField, err = NewRSRField(dc.ReqTypeField); err != nil { return nil, err } } dc.DirectionField = dirFld - if strings.HasPrefix(dc.DirectionField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.DirectionField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrDirectionField, err = NewRSRField(dc.DirectionField); err != nil { return nil, err } } dc.TenantField = tenantFld - if strings.HasPrefix(dc.TenantField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.TenantField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrTenantField, err = NewRSRField(dc.TenantField); err != nil { return nil, err } } dc.CategoryField = catFld - if strings.HasPrefix(dc.CategoryField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.CategoryField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrCategoryField, err = NewRSRField(dc.CategoryField); err != nil { return nil, err } } dc.AccountField = acntFld - if strings.HasPrefix(dc.AccountField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.AccountField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrAccountField, err = NewRSRField(dc.AccountField); err != nil { return nil, err } } dc.SubjectField = subjFld - if strings.HasPrefix(dc.SubjectField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.SubjectField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrSubjectField, err = NewRSRField(dc.SubjectField); err != nil { return nil, err } } dc.DestinationField = dstFld - if strings.HasPrefix(dc.DestinationField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.DestinationField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrDestinationField, err = NewRSRField(dc.DestinationField); err != nil { return nil, err } } dc.SetupTimeField = sTimeFld - if strings.HasPrefix(dc.SetupTimeField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.SetupTimeField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrSetupTimeField, err = NewRSRField(dc.SetupTimeField); err != nil { return nil, err } } dc.AnswerTimeField = aTimeFld - if strings.HasPrefix(dc.AnswerTimeField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.AnswerTimeField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrAnswerTimeField, err = NewRSRField(dc.AnswerTimeField); err != nil { return nil, err } } dc.UsageField = durFld - if strings.HasPrefix(dc.UsageField, REGEXP_PREFIX) { + if strings.HasPrefix(dc.UsageField, REGEXP_PREFIX) || strings.HasPrefix(dc.RunFilter, STATIC_VALUE_PREFIX) { if dc.rsrUsageField, err = NewRSRField(dc.UsageField); err != nil { return nil, err } @@ -94,6 +102,7 @@ func NewDerivedCharger(runId, reqTypeFld, dirFld, tenantFld, catFld, acntFld, su type DerivedCharger struct { RunId string // Unique runId in the chain + RunFilter string // Only run the charger if the filter matches ReqTypeField string // Field containing request type info, number in case of csv source, '^' as prefix in case of static values DirectionField string // Field containing direction info TenantField string // Field containing tenant info @@ -104,7 +113,8 @@ type DerivedCharger struct { SetupTimeField string // Field containing setup time information AnswerTimeField string // Field containing answer time information UsageField string // Field containing usage information - rsrReqTypeField *RSRField // Storage for compiled Regexp in case of RSRFields + rsrRunFilter *RSRField // Storage for compiled Regexp in case of RSRFields + rsrReqTypeField *RSRField rsrDirectionField *RSRField rsrTenantField *RSRField rsrCategoryField *RSRField @@ -136,7 +146,7 @@ func (dcs DerivedChargers) Append(dc *DerivedCharger) (DerivedChargers, error) { } func (dcs DerivedChargers) AppendDefaultRun() (DerivedChargers, error) { - dcDf, _ := NewDerivedCharger(DEFAULT_RUNID, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, + dcDf, _ := NewDerivedCharger(DEFAULT_RUNID, "", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT) return append(dcs, dcDf), nil } diff --git a/utils/derivedchargers_test.go b/utils/derivedchargers_test.go index 425cfb54d..52e2f1566 100644 --- a/utils/derivedchargers_test.go +++ b/utils/derivedchargers_test.go @@ -47,6 +47,7 @@ func TestAppendDerivedChargers(t *testing.T) { func TestNewDerivedCharger(t *testing.T) { edc1 := &DerivedCharger{ RunId: "test1", + RunFilter: "", ReqTypeField: "reqtype1", DirectionField: "direction1", TenantField: "tenant1", @@ -58,7 +59,7 @@ func TestNewDerivedCharger(t *testing.T) { AnswerTimeField: "answertime1", UsageField: "duration1", } - if dc1, err := NewDerivedCharger("test1", "reqtype1", "direction1", "tenant1", "tor1", "account1", "subject1", "destination1", + if dc1, err := NewDerivedCharger("test1", "", "reqtype1", "direction1", "tenant1", "tor1", "account1", "subject1", "destination1", "setuptime1", "answertime1", "duration1"); err != nil { t.Error("Unexpected error", err.Error) } else if !reflect.DeepEqual(edc1, dc1) { @@ -66,6 +67,7 @@ func TestNewDerivedCharger(t *testing.T) { } edc2 := &DerivedCharger{ RunId: "test2", + RunFilter: "^cdr_source/tdm_cdrs", ReqTypeField: "~reqtype2:s/sip:(.+)/$1/", DirectionField: "~direction2:s/sip:(.+)/$1/", TenantField: "~tenant2:s/sip:(.+)/$1/", @@ -77,6 +79,7 @@ func TestNewDerivedCharger(t *testing.T) { AnswerTimeField: "~answertime2:s/sip:(.+)/$1/", UsageField: "~duration2:s/sip:(.+)/$1/", } + edc2.rsrRunFilter, _ = NewRSRField("^cdr_source/tdm_cdrs") edc2.rsrReqTypeField, _ = NewRSRField("~reqtype2:s/sip:(.+)/$1/") edc2.rsrDirectionField, _ = NewRSRField("~direction2:s/sip:(.+)/$1/") edc2.rsrTenantField, _ = NewRSRField("~tenant2:s/sip:(.+)/$1/") @@ -88,6 +91,7 @@ func TestNewDerivedCharger(t *testing.T) { edc2.rsrAnswerTimeField, _ = NewRSRField("~answertime2:s/sip:(.+)/$1/") edc2.rsrUsageField, _ = NewRSRField("~duration2:s/sip:(.+)/$1/") if dc2, err := NewDerivedCharger("test2", + "^cdr_source/tdm_cdrs", "~reqtype2:s/sip:(.+)/$1/", "~direction2:s/sip:(.+)/$1/", "~tenant2:s/sip:(.+)/$1/", @@ -112,7 +116,7 @@ func TestDerivedChargersKey(t *testing.T) { func TestAppendDefaultRun(t *testing.T) { var dc1 DerivedChargers - dcDf := &DerivedCharger{RunId: DEFAULT_RUNID, ReqTypeField: META_DEFAULT, DirectionField: META_DEFAULT, + dcDf := &DerivedCharger{RunId: DEFAULT_RUNID, RunFilter: "", ReqTypeField: META_DEFAULT, DirectionField: META_DEFAULT, TenantField: META_DEFAULT, CategoryField: META_DEFAULT, AccountField: META_DEFAULT, SubjectField: META_DEFAULT, DestinationField: META_DEFAULT, SetupTimeField: META_DEFAULT, AnswerTimeField: META_DEFAULT, UsageField: META_DEFAULT} eDc1 := DerivedChargers{dcDf} @@ -120,7 +124,7 @@ func TestAppendDefaultRun(t *testing.T) { t.Error("Unexpected result.") } dc2 := DerivedChargers{ - &DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default", + &DerivedCharger{RunId: "extra1", RunFilter: "", ReqTypeField: "reqtype2", DirectionField: "*default", TenantField: "*default", CategoryField: "*default", AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"}, &DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default", AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},