Diameter - *grouped field type, filters in FieldFilter, fixes #318

This commit is contained in:
DanB
2015-12-11 16:48:59 +01:00
parent cc8ecc4e51
commit da8bc5f59e
6 changed files with 139 additions and 42 deletions

View File

@@ -68,7 +68,7 @@ func (self *DiameterAgent) handlers() diam.Handler {
func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProcessor) (*CCA, error) {
passesAllFilters := true
for _, fldFilter := range reqProcessor.RequestFilter {
if !ccr.passesFieldFilter(fldFilter) {
if passes, _ := ccr.passesFieldFilter(fldFilter); !passes {
passesAllFilters = false
}
}

View File

@@ -282,14 +282,6 @@ func avpValAsString(a *diam.AVP) string {
return dataVal[startIdx+1 : endIdx]
}
// Follows the implementation in the StorCdr
func (self *CCR) passesFieldFilter(fieldFilter *utils.RSRField) bool {
if fieldFilter == nil {
return true
}
return fieldFilter.FilterPasses(self.eventFieldValue(utils.RSRFields{fieldFilter}))
}
// Handler for meta functions
func (self *CCR) metaHandler(tag, arg string) (string, error) {
switch tag {
@@ -300,56 +292,106 @@ func (self *CCR) metaHandler(tag, arg string) (string, error) {
return "", nil
}
func (self *CCR) eventFieldValue(fldTpl utils.RSRFields) string {
func (self *CCR) avpsWithPath(rsrFld *utils.RSRField) ([]*diam.AVP, error) {
hierarchyPath := strings.Split(rsrFld.Id, utils.HIERARCHY_SEP)
hpIf := make([]interface{}, len(hierarchyPath))
for i, val := range hierarchyPath {
hpIf[i] = val
}
return self.diamMessage.FindAVPsWithPath(hpIf, dict.UndefinedVendorID)
}
// Follows the implementation in the StorCdr
func (self *CCR) passesFieldFilter(fieldFilter *utils.RSRField) (bool, int) {
if fieldFilter == nil {
return true, 0
}
avps, err := self.avpsWithPath(fieldFilter)
if err != nil {
return false, 0
} else if len(avps) == 0 {
return true, 0
}
for avpIdx, avpVal := range avps {
if fieldFilter.FilterPasses(avpValAsString(avpVal)) {
return true, avpIdx
}
}
return false, 0
}
func (self *CCR) eventFieldValue(fldTpl utils.RSRFields, avpIdx int) string {
var outVal string
for _, rsrTpl := range fldTpl {
if rsrTpl.IsStatic() {
outVal += rsrTpl.ParseValue("")
} else {
hierarchyPath := strings.Split(rsrTpl.Id, utils.HIERARCHY_SEP)
hpIf := make([]interface{}, len(hierarchyPath))
for i, val := range hierarchyPath {
hpIf[i] = val
}
matchingAvps, err := self.diamMessage.FindAVPsWithPath(hpIf, dict.UndefinedVendorID)
matchingAvps, err := self.avpsWithPath(rsrTpl)
if err != nil || len(matchingAvps) == 0 {
utils.Logger.Warning(fmt.Sprintf("<Diameter> Cannot find AVP for field template with id: %s, ignoring.", rsrTpl.Id))
continue // Filter not matching
}
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 += avpValAsString(matchingAvps[0])
outVal += avpValAsString(matchingAvps[avpIdx])
}
}
return outVal
}
func (self *CCR) fieldOutVal(cfgFld *config.CfgCdrField) (fmtValOut string, err error) {
var outVal string
switch cfgFld.Type {
case utils.META_FILLER:
outVal = cfgFld.Value.Id()
cfgFld.Padding = "right"
case utils.META_CONSTANT:
outVal = cfgFld.Value.Id()
case utils.META_HANDLER:
outVal, err = self.metaHandler(cfgFld.HandlerId, cfgFld.Layout)
if err != nil {
utils.Logger.Warning(fmt.Sprintf("<Diameter> Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error()))
}
case utils.META_COMPOSED:
outVal = self.eventFieldValue(cfgFld.Value, 0)
case utils.MetaGrouped: // GroupedAVP
passAtIndex := -1
matchedAllFilters := true
for _, fldFilter := range cfgFld.FieldFilter {
var pass bool
if pass, passAtIndex = self.passesFieldFilter(fldFilter); !pass {
matchedAllFilters = false
break
}
}
if !matchedAllFilters {
return "", nil // Not matching field filters, will have it empty
}
if passAtIndex == -1 {
passAtIndex = 0 // No filter
}
outVal = self.eventFieldValue(cfgFld.Value, passAtIndex)
}
if fmtValOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
utils.Logger.Warning(fmt.Sprintf("<Diameter> Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error()))
return "", err
}
return fmtValOut, nil
}
// Extracts data out of CCR into a SMGenericEvent based on the configured template
func (self *CCR) AsSMGenericEvent(cfgFlds []*config.CfgCdrField) (sessionmanager.SMGenericEvent, error) {
outMap := make(map[string]string) // work with it so we can append values to keys
outMap[utils.EVENT_NAME] = DIAMETER_CCR
for _, cfgFld := range cfgFlds {
var outVal string
var err error
switch cfgFld.Type {
case utils.META_FILLER:
outVal = cfgFld.Value.Id()
cfgFld.Padding = "right"
case utils.META_CONSTANT:
outVal = cfgFld.Value.Id()
case utils.META_HANDLER:
outVal, err = self.metaHandler(cfgFld.HandlerId, cfgFld.Layout)
if err != nil {
utils.Logger.Warning(fmt.Sprintf("<Diameter> Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error()))
}
case utils.META_COMPOSED:
outVal = self.eventFieldValue(cfgFld.Value)
}
fmtOut := outVal
if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
utils.Logger.Warning(fmt.Sprintf("<Diameter> Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error()))
fmtOut, err := self.fieldOutVal(cfgFld)
if err != nil {
return nil, err
}
if _, hasKey := outMap[cfgFld.FieldId]; !hasKey {

View File

@@ -19,10 +19,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package agents
import (
"fmt"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
"github.com/fiorix/go-diameter/diam"
"github.com/fiorix/go-diameter/diam/avp"
"github.com/fiorix/go-diameter/diam/datatype"
@@ -70,8 +71,6 @@ func TestUsageFromCCR(t *testing.T) {
}
if usage := usageFromCCR(1, 0, 360, time.Duration(360)*time.Second); usage != time.Duration(360)*time.Second {
t.Error(usage)
} else {
fmt.Printf("Usage: %v", usage)
}
}
@@ -82,3 +81,58 @@ func TestAvpValAsString(t *testing.T) {
t.Errorf("Expected: %s, received: %s", originHostStr, avpValStr)
}
}
func TestFieldOutVal(t *testing.T) {
m := diam.NewRequest(diam.CreditControl, 4, nil)
m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002"))
m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{
AVP: []*diam.AVP{
diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type
diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data
}})
m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{
AVP: []*diam.AVP{
diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(1)), // Subscription-Id-Type
diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("208708000003")), // Subscription-Id-Data
}})
m.NewAVP("Service-Identifier", avp.Mbit, 0, datatype.Unsigned32(0))
m.NewAVP("Requested-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{
AVP: []*diam.AVP{
diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(360))}}) // CC-Time
ccr := &CCR{diamMessage: m}
cfgFld := &config.CfgCdrField{Tag: "StaticTest", Type: utils.META_COMPOSED, FieldId: utils.TOR,
Value: utils.ParseRSRFieldsMustCompile("^*voice", utils.INFIELD_SEP), Mandatory: true}
eOut := "*voice"
if fldOut, err := ccr.fieldOutVal(cfgFld); err != nil {
t.Error(err)
} else if fldOut != eOut {
t.Errorf("Expecting: %s, received: %s", eOut, fldOut)
}
cfgFld = &config.CfgCdrField{Tag: "ComposedTest", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION,
Value: utils.ParseRSRFieldsMustCompile("Requested-Service-Unit>CC-Time", utils.INFIELD_SEP), Mandatory: true}
eOut = "360"
if fldOut, err := ccr.fieldOutVal(cfgFld); err != nil {
t.Error(err)
} else if fldOut != eOut {
t.Errorf("Expecting: %s, received: %s", eOut, fldOut)
}
// Without filter, we shoud get always the first subscriptionId
cfgFld = &config.CfgCdrField{Tag: "Grouped1", Type: utils.MetaGrouped, FieldId: "Account",
Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), Mandatory: true}
eOut = "33708000003"
if fldOut, err := ccr.fieldOutVal(cfgFld); err != nil {
t.Error(err)
} else if fldOut != eOut {
t.Errorf("Expecting: %s, received: %s", eOut, fldOut)
}
// Without groupedAVP, we shoud get the first subscriptionId
cfgFld = &config.CfgCdrField{Tag: "Grouped2", Type: utils.MetaGrouped, FieldId: "Account",
FieldFilter: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Type(1)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), Mandatory: true}
eOut = "208708000003"
if fldOut, err := ccr.fieldOutVal(cfgFld); err != nil {
t.Error(err)
} else if fldOut != eOut {
t.Errorf("Expecting: %s, received: %s", eOut, fldOut)
}
}

View File

@@ -204,7 +204,7 @@ func (self SMGenericEvent) GetOriginatorIP(fieldName string) string {
}
func (self SMGenericEvent) GetCdrSource() string {
return utils.SessionManagerGeneric // Needs to match the one in the SMGEvent.saveOperations
return utils.SMG // Needs to match the one in the SMGEvent.saveOperations
}
func (self SMGenericEvent) GetExtraFields() map[string]string {

View File

@@ -206,7 +206,7 @@ func (self *SMGSession) saveOperations() error {
var reply string
err := self.cdrsrv.LogCallCost(&engine.CallCostLog{
CgrId: self.eventStart.GetCgrId(self.timezone),
Source: utils.SessionManagerGeneric,
Source: utils.SMG,
RunId: self.runId,
CallCost: firstCC,
CheckDuplicate: true,

View File

@@ -261,7 +261,8 @@ const (
NegativePrefix = "!"
MatchStartPrefix = "^"
MatchEndPrefix = "$"
SessionManagerGeneric = "SMG"
SMG = "SMG"
MetaGrouped = "*grouped"
)
var (