diff --git a/agents/dmtagent.go b/agents/dmtagent.go index d5d7b7d39..0d3b0bfe1 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -109,6 +109,7 @@ func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestPro var maxUsage float64 processorVars := make(map[string]string) processorVars[CGRResultCode] = strconv.Itoa(diam.Success) + processorVars[CGRError] = "" if reqProcessor.DryRun { // DryRun does not send over network utils.Logger.Info(fmt.Sprintf(" SMGenericEvent: %+v", smgEv)) processorVars[CGRResultCode] = strconv.Itoa(diam.LimitedSuccess) diff --git a/agents/libdmt.go b/agents/libdmt.go index 30c3b2679..6d2ee67a8 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -221,7 +221,7 @@ func metaHandler(m *diam.Message, tag, arg string, dur time.Duration) (string, e // metaValueExponent will multiply the float value with the exponent provided. // Expects 2 arguments in template separated by | func metaValueExponent(m *diam.Message, argsTpl utils.RSRFields, roundingDecimals int) (string, error) { - valStr := composedFieldvalue(m, argsTpl, 0) + valStr := composedFieldvalue(m, argsTpl, 0, nil) handlerArgs := strings.Split(valStr, utils.HandlerArgSep) if len(handlerArgs) != 2 { return "", errors.New("Unexpected number of arguments") @@ -258,15 +258,14 @@ func passesFieldFilter(m *diam.Message, fieldFilter *utils.RSRField, processorVa if fieldFilter == nil { return true, 0 } + if val, hasIt := processorVars[fieldFilter.Id]; hasIt { // ProcessorVars have priority + if fieldFilter.FilterPasses(val) { + return true, 0 + } + return false, 0 + } avps, err := avpsWithPath(m, fieldFilter) if err != nil { - if strings.HasPrefix(err.Error(), "Could not find AVP") { - if val, hasIt := processorVars[fieldFilter.Id]; hasIt { // Could not find it in the message, try to match it in processorVars - if fieldFilter.FilterPasses(val) { - return true, -1 - } - } - } return false, 0 } for avpIdx, avpVal := range avps { // First match wins due to index @@ -277,12 +276,16 @@ func passesFieldFilter(m *diam.Message, fieldFilter *utils.RSRField, processorVa return false, 0 } -func composedFieldvalue(m *diam.Message, outTpl utils.RSRFields, avpIdx int) string { +func composedFieldvalue(m *diam.Message, outTpl utils.RSRFields, avpIdx int, processorVars map[string]string) string { var outVal string for _, rsrTpl := range outTpl { if rsrTpl.IsStatic() { outVal += rsrTpl.ParseValue("") } else { + if val, hasIt := processorVars[rsrTpl.Id]; hasIt { // ProcessorVars have priority + outVal += rsrTpl.ParseValue(val) + continue + } matchingAvps, err := avpsWithPath(m, rsrTpl) if err != nil || len(matchingAvps) == 0 { utils.Logger.Warning(fmt.Sprintf(" Cannot find AVP for field template with id: %s, ignoring.", rsrTpl.Id)) @@ -377,9 +380,9 @@ func fieldOutVal(m *diam.Message, cfgFld *config.CfgCdrField, extraParam interfa } } case utils.META_COMPOSED: - outVal = composedFieldvalue(m, cfgFld.Value, 0) + outVal = composedFieldvalue(m, cfgFld.Value, 0, processorVars) case utils.MetaGrouped: // GroupedAVP - outVal = composedFieldvalue(m, cfgFld.Value, passAtIndex) + outVal = composedFieldvalue(m, cfgFld.Value, passAtIndex, processorVars) } if fmtValOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Warning(fmt.Sprintf(" Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error())) diff --git a/data/conf/samples/dmtagent/diameter_processors.json b/data/conf/samples/dmtagent/diameter_processors.json index ab906b0a7..df3d23737 100644 --- a/data/conf/samples/dmtagent/diameter_processors.json +++ b/data/conf/samples/dmtagent/diameter_processors.json @@ -2,6 +2,10 @@ "diameter_agent": { "request_processors": [ + { + "id": "*default", // formal identifier of this processor + "request_filter": "Service-Context-Id(nonexistent)", // cancel matching of this processor + }, { "id": "dryrun1", // formal identifier of this processor "dry_run": true, // do not send the events to SMG, just log them @@ -60,6 +64,12 @@ {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, {"tag": "Usage", "field_id": "Usage", "type": "*composed", "value": "Requested-Service-Unit>CC-Time", "mandatory": true}, ], + "cca_fields":[ + {"tag": "ResultCode", "field_filter":"CGRError(ACCOUNT_NOT_FOUND)", + "field_id": "Result-Code", "type": "*constant", "value": "5030"}, + {"tag": "ResultCode", "field_filter":"CGRError(USER_NOT_FOUND)", + "field_id": "Result-Code", "type": "*constant", "value": "5030"}, + ], }, ], diff --git a/data/conf/samples/dmtagent/voice.json b/data/conf/samples/dmtagent/voice.json index 24ab07879..a1b369ea4 100644 --- a/data/conf/samples/dmtagent/voice.json +++ b/data/conf/samples/dmtagent/voice.json @@ -4,9 +4,38 @@ "diameter_agent": { "request_processors": [ { - "id": "*default", // formal identifier of this processor + "id": "VoiceInit", // formal identifier of this processor "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^voice)", // filter requests processed by this processor + "request_filter": "Service-Context-Id(^voice);CC-Request-Type(1)", // filter requests processed by this processor + "continue_on_success": false, // continue to the next template if executed + "ccr_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*voice", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, + {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, + {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, + ], + "cca_fields":[ + {"tag": "ResultCode", "field_filter":"CGRError(ACCOUNT_NOT_FOUND)", + "field_id": "Result-Code", "type": "*constant", "value": "5030"}, + {"tag": "ResultCode", "field_filter":"CGRError(USER_NOT_FOUND)", + "field_id": "Result-Code", "type": "*constant", "value": "5030"}, + {"tag": "GrantedUnits", "field_filter":"CGRError(^$)", + "field_id": "Granted-Service-Unit>CC-Time", "type": "*composed", "value": "CGRMaxUsage", "mandatory": true}, + ], + }, + { + "id": "VoiceUpdate", // formal identifier of this processor + "dry_run": false, // do not send the events to SMG, just log them + "request_filter": "Service-Context-Id(^voice);CC-Request-Type(2)", // filter requests processed by this processor "continue_on_success": false, // continue to the next template if executed "ccr_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*voice", "mandatory": true}, @@ -24,7 +53,33 @@ {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, ], "cca_fields":[ // fields returned in CCA - {"tag": "GrantedUnits", "field_id": "Granted-Service-Unit>CC-Time", "type": "*handler", "handler_id": "*cca_usage", "mandatory": true}, + {"tag": "GrantedUnits", "field_filter":"CGRError(^$)", + "field_id": "Granted-Service-Unit>CC-Time", "type": "*composed", "value": "CGRMaxUsage", "mandatory": true}, + ], + }, + { + "id": "VoiceTerminate", // formal identifier of this processor + "dry_run": false, // do not send the events to SMG, just log them + "request_filter": "Service-Context-Id(^voice);CC-Request-Type(3)", // filter requests processed by this processor + "continue_on_success": false, // continue to the next template if executed + "ccr_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*voice", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, + {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, + {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, + ], + "cca_fields":[ // fields returned in CCA + {"tag": "GrantedUnits", "field_filter":"CGRError(^$)", + "field_id": "Granted-Service-Unit>CC-Time", "type": "*composed", "value": "CGRMaxUsage", "mandatory": true}, ], }, ], diff --git a/utils/rsrfield.go b/utils/rsrfield.go index b454500fb..d8ca48c2b 100644 --- a/utils/rsrfield.go +++ b/utils/rsrfield.go @@ -166,6 +166,9 @@ func (rsrFltr *RSRFilter) Pass(val string) bool { if rsrFltr.filterRule[:1] == REGEXP_PREFIX { return rsrFltr.fltrRgxp.MatchString(val) != rsrFltr.negative } + if rsrFltr.filterRule == "^$" { // Special case to test empty value + return len(val) == 0 != rsrFltr.negative + } if rsrFltr.filterRule[:1] == MatchStartPrefix { return strings.HasPrefix(val, rsrFltr.filterRule[1:]) != rsrFltr.negative } diff --git a/utils/rsrfield_test.go b/utils/rsrfield_test.go index 1da60b703..c98c32fb1 100644 --- a/utils/rsrfield_test.go +++ b/utils/rsrfield_test.go @@ -341,4 +341,14 @@ func TestRSRFilterPass(t *testing.T) { if fltr.Pass("CGRateS") { t.Error("Passing!") } + fltr, err = NewRSRFilter("^$") // Empty value + if err != nil { + t.Error(err) + } + if fltr.Pass("CGRateS") { + t.Error("Passing!") + } + if !fltr.Pass("") { + t.Error("Not passing!") + } }