From 180ad31f5cbced5b5ebb170536c1eb838b023232 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 7 May 2018 18:50:23 +0200 Subject: [PATCH] Diameter integrating AttributeS/ResourceS/Suppliers/Threshold APIs, fixes #999 --- agents/dmtagent.go | 34 +------- agents/dmtagent_it_test.go | 42 +++++----- agents/libdmt.go | 82 ++++++++++++------- agents/librad.go | 2 +- data/conf/samples/dmtagent/data.json | 15 ++-- .../samples/dmtagent/diameter_processors.json | 4 +- data/conf/samples/dmtagent/voice.json | 24 ++---- sessions/sessions.go | 4 + utils/errors.go | 1 + 9 files changed, 100 insertions(+), 108 deletions(-) diff --git a/agents/dmtagent.go b/agents/dmtagent.go index 5466b6484..8d3a24153 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -133,7 +133,6 @@ func (da DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProce return false, ErrDiameterRatingFailed } } - procVars[CGRResultCode] = strconv.Itoa(diam.Success) if reqProcessor.DryRun { // DryRun does not send over network utils.Logger.Info(fmt.Sprintf(" SMGenericEvent: %+v", smgEv)) procVars[CGRResultCode] = strconv.Itoa(diam.LimitedSuccess) @@ -190,36 +189,11 @@ func (da DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProce } } } - /*if err != nil { - utils.Logger.Err(fmt.Sprintf(" Processing message: %+v, API error: %s", ccr.diamMessage, err)) - switch { // Prettify some errors - case strings.HasSuffix(err.Error(), utils.ErrAccountNotFound.Error()): - procVars[CGRError] = utils.ErrAccountNotFound.Error() - case strings.HasSuffix(err.Error(), utils.ErrUserNotFound.Error()): - procVars[CGRError] = utils.ErrUserNotFound.Error() - case strings.HasSuffix(err.Error(), utils.ErrInsufficientCredit.Error()): - procVars[CGRError] = utils.ErrInsufficientCredit.Error() - case strings.HasSuffix(err.Error(), utils.ErrAccountDisabled.Error()): - procVars[CGRError] = utils.ErrAccountDisabled.Error() - case strings.HasSuffix(err.Error(), utils.ErrRatingPlanNotFound.Error()): - procVars[CGRError] = utils.ErrRatingPlanNotFound.Error() - case strings.HasSuffix(err.Error(), utils.ErrUnauthorizedDestination.Error()): - procVars[CGRError] = utils.ErrUnauthorizedDestination.Error() - default: // Unknown error - procVars[CGRError] = err.Error() - procVars[CGRResultCode] = strconv.Itoa(DiameterRatingFailed) - } - } - */ - /*if prevMaxUsageStr, hasKey := procVars[CGRMaxUsage]; hasKey { - prevMaxUsage, _ := utils.ParseDurationWithNanosecs(prevMaxUsageStr) - if prevMaxUsage < maxUsage { - maxUsage = prevMaxUsage - } - } - */ } - diamCode, _ := procVars.valAsString(CGRResultCode) + diamCode := strconv.Itoa(diam.Success) + if procVars.hasVar(CGRResultCode) { + diamCode, _ = procVars.valAsString(CGRResultCode) + } if err := messageSetAVPsWithPath(cca.diamMessage, []interface{}{"Result-Code"}, diamCode, false, da.cgrCfg.DiameterAgentCfg().Timezone); err != nil { return false, err diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 7ff1ea92f..b080fe393 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -207,14 +207,12 @@ func TestDmtAgentResetStorDb(t *testing.T) { } } -/* // Start CGR Engine func TestDmtAgentStartEngine(t *testing.T) { if _, err := engine.StopStartEngine(daCfgPath, 4000); err != nil { t.Fatal(err) } } -*/ // Connect rpc client to rater func TestDmtAgentApierRpcConn(t *testing.T) { @@ -245,8 +243,8 @@ func TestDmtAgentConnectDiameterClient(t *testing.T) { // cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:47:26Z"' func TestDmtAgentSendCCRInit(t *testing.T) { - cdr := &engine.CDR{CGRID: utils.Sha1("testccr1", - time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), + cdr := &engine.CDR{ + CGRID: utils.Sha1("testccr1", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderID: 123, ToR: utils.VOICE, OriginID: "testccr1", OriginHost: "192.168.1.1", Source: utils.UNIT_TEST, RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", @@ -259,7 +257,8 @@ func TestDmtAgentSendCCRInit(t *testing.T) { ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, - daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, + daCfg.DiameterAgentCfg().ProductName, + utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) m, err := ccr.AsDiameterMessage() if err != nil { @@ -366,14 +365,23 @@ func TestDmtAgentSendCCRUpdate2(t *testing.T) { } func TestDmtAgentSendCCRTerminate(t *testing.T) { - cdr := &engine.CDR{CGRID: utils.Sha1("testccr1", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderID: 123, ToR: utils.VOICE, - OriginID: "testccr1", OriginHost: "192.168.1.1", Source: utils.UNIT_TEST, RequestType: utils.META_RATED, - Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", - SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), RunID: utils.DEFAULT_RUNID, - Usage: time.Duration(610) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, + cdr := &engine.CDR{ + CGRID: utils.Sha1("testccr1", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), + OrderID: 123, ToR: utils.VOICE, + OriginID: "testccr1", OriginHost: "192.168.1.1", + Source: utils.UNIT_TEST, RequestType: utils.META_RATED, + Tenant: "cgrates.org", Category: "call", Account: "1001", + Subject: "1001", Destination: "1004", + SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), + AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), + RunID: utils.DEFAULT_RUNID, + Usage: time.Duration(610) * time.Second, + ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, } - ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, - daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, true) + ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, + daCfg.DiameterAgentCfg().VendorId, + daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, + daCfg.DiameterAgentCfg().DebitInterval, true) m, err := ccr.AsDiameterMessage() if err != nil { t.Error(err) @@ -386,20 +394,14 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) { if msg == nil { t.Fatal("No answer to CCR terminate received") } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Granted-Service-Unit not found") - } else if strCCTime := avpValAsString(avps[0]); strCCTime != "0" { - t.Errorf("Expecting 0, received: %s", strCCTime) - } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} eAcntVal := 9.243500 if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 - t.Errorf("Expected: %v, received: %v", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) + t.Errorf("Expected: %v, received: %v", + eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) } } diff --git a/agents/libdmt.go b/agents/libdmt.go index 62b9ae276..531a122d8 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -279,12 +279,18 @@ func passesFieldFilter(m *diam.Message, fieldFilter *utils.RSRField, procVars pr if fieldFilter == nil { return true, 0 } - if val, hasIt := procVars[fieldFilter.Id]; hasIt { // ProcessorVars have priority - valStr, _ := utils.CastFieldIfToString(val) - if fieldFilter.FilterPasses(valStr) { - return true, 0 + // check procVars before AVPs + if val, err := procVars.valAsString(fieldFilter.Id); err != utils.ErrNotFoundNoCaps { + if err != nil { + utils.Logger.Warning( + fmt.Sprintf(" parsing value: <%s> as string, error: <%s>", + fieldFilter.Id, err.Error())) + return false, 0 } - return false, 0 + if !fieldFilter.FilterPasses(val) { + return false, 0 + } + return true, 0 } avps, err := avpsWithPath(m, fieldFilter) if err != nil { @@ -303,36 +309,52 @@ func passesFieldFilter(m *diam.Message, fieldFilter *utils.RSRField, procVars pr return false, 0 } -func composedFieldvalue(m *diam.Message, outTpl utils.RSRFields, avpIdx int, procVars processorVars) string { - var outVal string +func composedFieldvalue(m *diam.Message, outTpl utils.RSRFields, avpIdx int, procVars processorVars) (outVal string) { + var err error for _, rsrTpl := range outTpl { - if rsrTpl.IsStatic() { - outVal += rsrTpl.ParseValue("") - } else { - if val, hasIt := procVars[rsrTpl.Id]; hasIt { // ProcessorVars have priority - valStr, _ := utils.CastFieldIfToString(val) - outVal += rsrTpl.ParseValue(valStr) - continue - } - matchingAvps, err := avpsWithPath(m, rsrTpl) - if err != nil || len(matchingAvps) == 0 { - if err != nil { - utils.Logger.Err(fmt.Sprintf(" Error matching AVPS: %s", err.Error())) + var valToParse string + if !rsrTpl.IsStatic() { // for Static we will parse empty valToParse bellow + // check procVars before AVPs + if valToParse, err = procVars.valAsString(rsrTpl.Id); err != nil { + if err != utils.ErrNotFoundNoCaps { + utils.Logger.Warning( + fmt.Sprintf("<%s> %s", utils.DiameterAgent, err.Error())) + continue } - continue + // not found in processorVars, look in AVPs + // AVPs from here + matchingAvps, err := avpsWithPath(m, rsrTpl) + if err != nil || len(matchingAvps) == 0 { + if err != nil { + utils.Logger.Err(fmt.Sprintf("<%s> Error matching AVPS: %s", + utils.DiameterAgent, err.Error())) + } + continue + } + if len(matchingAvps) <= avpIdx { + utils.Logger.Warning( + fmt.Sprintf("<%s> Cannot retrieve AVP with index %d for field template with id: %s", + utils.DiameterAgent, avpIdx, rsrTpl.Id)) + continue // Not convertible, ignore + } + if matchingAvps[0].Data.Type() == diam.GroupedAVPType { + utils.Logger.Warning( + fmt.Sprintf("<%s> Value for field template with id: %s is matching a group AVP, ignoring.", + utils.DiameterAgent, rsrTpl.Id)) + continue // Not convertible, ignore + } + valToParse = avpValAsString(matchingAvps[avpIdx]) } - if len(matchingAvps) <= avpIdx { - utils.Logger.Warning(fmt.Sprintf(" Cannot retrieve AVP with index %d for field template with id: %s", avpIdx, rsrTpl.Id)) - continue // Not convertible, ignore - } - if matchingAvps[0].Data.Type() == diam.GroupedAVPType { - utils.Logger.Warning(fmt.Sprintf(" Value for field template with id: %s is matching a group AVP, ignoring.", rsrTpl.Id)) - continue // Not convertible, ignore - } - outVal += rsrTpl.ParseValue(avpValAsString(matchingAvps[avpIdx])) + } + if parsed, err := rsrTpl.Parse(valToParse); err != nil { + utils.Logger.Warning( + fmt.Sprintf("<%s> %s", + utils.DiameterAgent, err.Error())) + } else { + outVal += parsed } } - return outVal + return } // Used to return the encoded value based on what AVP understands for it's type diff --git a/agents/librad.go b/agents/librad.go index 0825cf57b..fa7fc64de 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -76,7 +76,7 @@ func (pv processorVars) valAsString(fldPath string) (val string, err error) { fldName = utils.MetaCGRReply } if !pv.hasVar(fldName) { - return "", errors.New("not found") + return "", utils.ErrNotFoundNoCaps } if fldName == utils.MetaCGRReply { cgrRply := pv[utils.MetaCGRReply].(utils.CGRReply) diff --git a/data/conf/samples/dmtagent/data.json b/data/conf/samples/dmtagent/data.json index c10765409..b3dba2295 100644 --- a/data/conf/samples/dmtagent/data.json +++ b/data/conf/samples/dmtagent/data.json @@ -22,8 +22,8 @@ {"tag": "Usage", "field_id": "Usage", "type": "*constant", "value": "2048"}, ], "cca_fields": [ - {"tag": "ResultCode", "field_id": "Result-Code", "type": "*constant", "value": "^2001"}, - {"tag": "ResultCode", "field_filter": "CGRMaxUsage(0)", "field_id": "Result-Code", "type": "*constant", "value": "4010"}, + {"tag": "ResultCode", "field_filter": "*cgrReply>MaxUsage(^0$)", + "field_id": "Result-Code", "type": "*constant", "value": "4010"}, ], }, { @@ -49,8 +49,8 @@ "value": "Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets;^|;Multiple-Services-Credit-Control>Used-Service-Unit>CC-Output-Octets"}, ], "cca_fields": [ - {"tag": "ResultCode", "field_id": "Result-Code", "type": "*constant", "value": "^2001"}, - {"tag": "ResultCode", "field_filter": "CGRMaxUsage(0)", "field_id": "Result-Code", "type": "*constant", "value": "4010"}, + {"tag": "ResultCode", "field_filter": "*cgrReply>MaxUsage(^0$)", + "field_id": "Result-Code", "type": "*constant", "value": "4010"}, ], }, { @@ -76,8 +76,8 @@ "value": "Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets;^|;Multiple-Services-Credit-Control>Used-Service-Unit>CC-Output-Octets"}, ], "cca_fields": [ - {"tag": "ResultCode", "field_id": "Result-Code", "type": "*constant", "value": "^2001"}, - {"tag": "ResultCode", "field_filter": "CGRMaxUsage(0)", "field_id": "Result-Code", "type": "*constant", "value": "4010"}, + {"tag": "ResultCode", "field_filter": "*cgrReply>MaxUsage(^0$)", + "field_id": "Result-Code", "type": "*constant", "value": "4010"}, ], }, { @@ -100,9 +100,6 @@ {"tag": "LastUsed", "field_id": "LastUsed", "type": "*handler", "handler_id": "*sum", "value": "Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets;^|;Multiple-Services-Credit-Control>Used-Service-Unit>CC-Output-Octets"}, ], - "cca_fields": [ - {"tag": "ResultCode", "field_id": "Result-Code", "type": "*constant", "value": "^2001"} - ], }, ] } diff --git a/data/conf/samples/dmtagent/diameter_processors.json b/data/conf/samples/dmtagent/diameter_processors.json index df3d23737..875b811dd 100644 --- a/data/conf/samples/dmtagent/diameter_processors.json +++ b/data/conf/samples/dmtagent/diameter_processors.json @@ -65,9 +65,9 @@ {"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)", + {"tag": "ResultCode", "field_filter":"*cgrReply>Error(ACCOUNT_NOT_FOUND)", "field_id": "Result-Code", "type": "*constant", "value": "5030"}, - {"tag": "ResultCode", "field_filter":"CGRError(USER_NOT_FOUND)", + {"tag": "ResultCode", "field_filter":"*cgrReply>Error(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 5d67b2bbf..3ac832d34 100644 --- a/data/conf/samples/dmtagent/voice.json +++ b/data/conf/samples/dmtagent/voice.json @@ -25,14 +25,13 @@ {"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)", + {"tag": "ResultCode", "field_filter":"*cgrReply>Error(ACCOUNT_NOT_FOUND)", "field_id": "Result-Code", "type": "*constant", "value": "5030"}, - {"tag": "ResultCode", "field_filter":"CGRError(USER_NOT_FOUND)", + {"tag": "ResultCode", "field_filter":"*cgrReply>Error(USER_NOT_FOUND)", "field_id": "Result-Code", "type": "*constant", "value": "5030"}, - {"tag": "GrantedUnits", "field_filter":"CGRError(^$)", - "field_id": "Granted-Service-Unit>CC-Time", - "type": "*handler", "handler_id": "*value_exponent", - "value": "CGRMaxUsage;^|-9", "mandatory": true}, + {"tag": "GrantedUnits", "field_filter":"*cgrReply>Error(^$)", + "field_id": "Granted-Service-Unit>CC-Time", "type": "*composed", + "value": "*cgrReply>MaxUsage{*duration_seconds}", "mandatory": true}, ], }, { @@ -56,10 +55,9 @@ {"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": "*handler", "handler_id": "*value_exponent", - "value": "CGRMaxUsage;^|-9", "mandatory": true}, + {"tag": "GrantedUnits", "field_filter":"*cgrReply>Error(^$)", + "field_id": "Granted-Service-Unit>CC-Time", "type": "*composed", + "value": "*cgrReply>MaxUsage{*duration_seconds}", "mandatory": true}, ], }, { @@ -82,12 +80,6 @@ {"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": "*handler", "handler_id": "*value_exponent", - "value": "CGRMaxUsage;^|-9", "mandatory": true}, - ], }, ], }, diff --git a/sessions/sessions.go b/sessions/sessions.go index 5e342d814..5d5d80011 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -1348,6 +1348,7 @@ func (v1AuthReply *V1AuthorizeReply) AsCGRReply() (cgrReply utils.CGRReply, err if v1AuthReply.ThresholdHits != nil { cgrReply[utils.CapThresholdHits] = *v1AuthReply.ThresholdHits } + cgrReply[utils.Error] = "" // so we can compare in filters return } @@ -1545,6 +1546,7 @@ func (v1Rply *V1InitSessionReply) AsCGRReply() (cgrReply utils.CGRReply, err err if v1Rply.ThresholdHits != nil { cgrReply[utils.CapThresholdHits] = *v1Rply.ThresholdHits } + cgrReply[utils.Error] = "" return } @@ -1705,6 +1707,7 @@ func (v1Rply *V1UpdateSessionReply) AsCGRReply() (cgrReply utils.CGRReply, err e if v1Rply.MaxUsage != nil { cgrReply[utils.CapMaxUsage] = *v1Rply.MaxUsage } + cgrReply[utils.Error] = "" return } @@ -1874,6 +1877,7 @@ func (v1Rply *V1ProcessEventReply) AsCGRReply() (cgrReply utils.CGRReply, err er } cgrReply[utils.CapAttributes] = attrs } + cgrReply[utils.Error] = "" return } diff --git a/utils/errors.go b/utils/errors.go index 0515915b4..466f50346 100644 --- a/utils/errors.go +++ b/utils/errors.go @@ -49,6 +49,7 @@ var ( ErrPartiallyExecuted = errors.New("PARTIALLY_EXECUTED") ErrMaxUsageExceeded = errors.New("MAX_USAGE_EXCEEDED") ErrUnallocatedResource = errors.New("UNALLOCATED_RESOURCE") + ErrNotFoundNoCaps = errors.New("not found") ) // NewCGRError initialises a new CGRError