Diameter integrating AttributeS/ResourceS/Suppliers/Threshold APIs, fixes #999

This commit is contained in:
DanB
2018-05-07 18:50:23 +02:00
parent eb95191e9e
commit 180ad31f5c
9 changed files with 100 additions and 108 deletions

View File

@@ -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("<DiameterAgent> 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("<DiameterAgent> 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

View File

@@ -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())
}
}

View File

@@ -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("<RSRFilter> 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("<DiameterAgent> 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("<Diameter> 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("<Diameter> 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

View File

@@ -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)

View File

@@ -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"}
],
},
]
}

View File

@@ -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"},
],
},

View File

@@ -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},
],
},
],
},

View File

@@ -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
}

View File

@@ -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