DiameterAgent forcing usage 0 when debit not possible, replacing CGRReply with NavigableMap

This commit is contained in:
DanB
2018-06-08 16:44:19 +02:00
parent df9ea8e3f7
commit dde4991d4e
10 changed files with 72 additions and 208 deletions

View File

@@ -174,12 +174,15 @@ func (da DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProce
if ccr.CCRequestType == 3 {
if err = da.sessionS.Call(utils.SessionSv1TerminateSession,
procVars.asV1TerminateSessionArgs(cgrEv), &rpl); err != nil {
procVars[utils.MetaCGRReply] = utils.CGRReply{utils.Error: err.Error()}
procVars[utils.MetaCGRReply] = map[string]interface{}{utils.Error: err.Error()}
}
} else if ccr.CCRequestType == 4 {
var evntRply sessions.V1ProcessEventReply
err = da.sessionS.Call(utils.SessionSv1ProcessEvent,
procVars.asV1ProcessEventArgs(cgrEv), &evntRply)
if evntRply.MaxUsage != nil && *evntRply.MaxUsage == 0 {
cgrEv.Event[utils.Usage] = 0 // prevent CDR to be written
}
if procVars[utils.MetaCGRReply], err = utils.NewCGRReply(&evntRply, err); err != nil {
return
}
@@ -189,7 +192,7 @@ func (da DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProce
!strings.HasSuffix(err.Error(), utils.ErrNoActiveSession.Error())) { // Check if CDR requires session
if errCdr := da.sessionS.Call(utils.SessionSv1ProcessCDR, *cgrEv, &rpl); errCdr != nil {
err = errCdr
procVars[utils.MetaCGRReply] = utils.CGRReply{utils.Error: err.Error()}
procVars[utils.MetaCGRReply] = map[string]interface{}{utils.Error: err.Error()}
}
}
}

View File

@@ -226,7 +226,7 @@ func TestFieldOutVal(t *testing.T) {
FieldFilter: utils.ParseRSRFieldsMustCompile("*cgrReply>Error(^$);*cgrReply>MaxUsage(!300);*cgrReply>MaxUsage(!0)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), Mandatory: true}
procVars := processorVars{
utils.MetaCGRReply: utils.CGRReply{
utils.MetaCGRReply: utils.NavigableMap{
utils.Error: "RALS_ERROR:NOT_FOUND",
},
}
@@ -526,12 +526,13 @@ func TestPassesFieldFilter(t *testing.T) {
t.Error("Does not pass")
}
procVars := processorVars{
utils.MetaCGRReply: utils.CGRReply{
utils.MetaCGRReply: map[string]interface{}{
utils.CapAttributes: map[string]interface{}{
"RadReply": "AccessAccept",
utils.Account: "1001",
},
utils.CapMaxUsage: time.Duration(0),
utils.Error: "",
},
}
if pass, _ := passesFieldFilter(nil,
@@ -544,4 +545,9 @@ func TestPassesFieldFilter(t *testing.T) {
procVars); !pass {
t.Error("not passing valid filter")
}
if pass, _ := passesFieldFilter(nil,
utils.ParseRSRFieldsMustCompile("*cgrReply>Error(^$)", utils.INFIELD_SEP)[0],
procVars); !pass {
t.Error("not passing valid filter")
}
}

View File

@@ -61,11 +61,7 @@ func (pv processorVars) valAsInterface(fldPath string) (val interface{}, err err
err = errors.New("not found")
return
}
if fldName == utils.MetaCGRReply {
cgrRply := pv[utils.MetaCGRReply].(utils.CGRReply)
return cgrRply.GetField(fldPath, utils.HIERARCHY_SEP)
}
return pv[fldName], nil
return utils.NavigableMap(pv).GetField(fldPath, utils.HIERARCHY_SEP)
}
// valAsString returns the string value for fldName
@@ -78,17 +74,7 @@ func (pv processorVars) valAsString(fldPath string) (val string, err error) {
if !pv.hasVar(fldName) {
return "", utils.ErrNotFoundNoCaps
}
if fldName == utils.MetaCGRReply {
cgrRply := pv[utils.MetaCGRReply].(utils.CGRReply)
return cgrRply.GetFieldAsString(fldPath, utils.HIERARCHY_SEP)
}
if valIface, hasIt := pv[fldName]; hasIt {
var canCast bool
if val, canCast = utils.CastFieldIfToString(valIface); !canCast {
return "", fmt.Errorf("cannot cast field <%s> to string", fldPath)
}
}
return
return utils.NavigableMap(pv).GetFieldAsString(fldPath, utils.HIERARCHY_SEP)
}
// asV1AuthorizeArgs returns the arguments needed by SessionSv1.AuthorizeEvent

View File

@@ -404,7 +404,7 @@ func TestRadReplyAppendAttributes(t *testing.T) {
Value: utils.ParseRSRFieldsMustCompile("*cgrReply>MaxUsage{*duration_seconds}", utils.INFIELD_SEP)},
}
procVars := make(processorVars)
procVars[utils.MetaCGRReply] = utils.CGRReply{
procVars[utils.MetaCGRReply] = map[string]interface{}{
utils.CapAttributes: map[string]interface{}{
"RadReply": "AccessAccept",
utils.Account: "1001",

View File

@@ -195,12 +195,12 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
var rpl string
if err = ra.sessionS.Call(utils.SessionSv1TerminateSession,
procVars.asV1TerminateSessionArgs(cgrEv), &rpl); err != nil {
procVars[utils.MetaCGRReply] = utils.CGRReply{utils.Error: err.Error()}
procVars[utils.MetaCGRReply] = map[string]interface{}{utils.Error: err.Error()}
}
if ra.cgrCfg.RadiusAgentCfg().CreateCDR {
if errCdr := ra.sessionS.Call(utils.SessionSv1ProcessCDR, *cgrEv, &rpl); errCdr != nil {
err = errCdr
procVars[utils.MetaCGRReply] = utils.CGRReply{utils.Error: err.Error()}
procVars[utils.MetaCGRReply] = map[string]interface{}{utils.Error: err.Error()}
}
}
if err != nil {

View File

@@ -1327,7 +1327,7 @@ type V1AuthorizeReply struct {
}
// AsCGRReply is part of utils.CGRReplier interface
func (v1AuthReply *V1AuthorizeReply) AsCGRReply() (cgrReply utils.CGRReply, err error) {
func (v1AuthReply *V1AuthorizeReply) AsNavigableMap() (cgrReply map[string]interface{}, err error) {
cgrReply = make(map[string]interface{})
if v1AuthReply.Attributes != nil {
attrs := make(map[string]interface{})
@@ -1542,7 +1542,7 @@ type V1InitSessionReply struct {
}
// AsCGRReply is part of utils.CGRReplier interface
func (v1Rply *V1InitSessionReply) AsCGRReply() (cgrReply utils.CGRReply, err error) {
func (v1Rply *V1InitSessionReply) AsNavigableMap() (cgrReply map[string]interface{}, err error) {
cgrReply = make(map[string]interface{})
if v1Rply.Attributes != nil {
attrs := make(map[string]interface{})
@@ -1722,7 +1722,7 @@ type V1UpdateSessionReply struct {
}
// AsCGRReply is part of utils.CGRReplier interface
func (v1Rply *V1UpdateSessionReply) AsCGRReply() (cgrReply utils.CGRReply, err error) {
func (v1Rply *V1UpdateSessionReply) AsNavigableMap() (cgrReply map[string]interface{}, err error) {
cgrReply = make(map[string]interface{})
if v1Rply.Attributes != nil {
attrs := make(map[string]interface{})
@@ -1889,7 +1889,7 @@ type V1ProcessEventReply struct {
}
// AsCGRReply is part of utils.CGRReplier interface
func (v1Rply *V1ProcessEventReply) AsCGRReply() (cgrReply utils.CGRReply, err error) {
func (v1Rply *V1ProcessEventReply) AsNavigableMap() (cgrReply map[string]interface{}, err error) {
cgrReply = make(map[string]interface{})
if v1Rply.MaxUsage != nil {
cgrReply[utils.CapMaxUsage] = *v1Rply.MaxUsage

View File

@@ -1,94 +0,0 @@
/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOev. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package utils
import (
"errors"
"fmt"
"strings"
)
// CGRReplier is the interface supported by replies convertible to CGRReply
type CGRReplier interface {
AsCGRReply() (CGRReply, error)
}
func NewCGRReply(rply CGRReplier, errRply error) (cgrReply CGRReply, err error) {
if errRply != nil {
return CGRReply{Error: errRply.Error()}, nil
}
if cgrReply, err = rply.AsCGRReply(); err != nil {
return
}
cgrReply[Error] = "" // enforce empty error
return
}
// CGRReply represents the CGRateS answer which can be used in templates
// it can be layered, case when interface{} will be castable into map[string]interface{}
type CGRReply map[string]interface{}
// GetField returns the field value as interface{} for the path specified
func (cgrReply CGRReply) GetField(fldPath string, sep string) (fldVal interface{}, err error) {
path := strings.Split(fldPath, sep)
lenPath := len(path)
if lenPath == 0 {
err = errors.New("empty field path")
return
}
if path[0] == MetaCGRReply {
path = path[1:]
lenPath -= 1
}
lastMp := cgrReply // last map when layered
var canCast bool
for i, spath := range path {
if i == lenPath-1 { // lastElement
var has bool
fldVal, has = lastMp[spath]
if !has {
err = fmt.Errorf("no field with path: <%s>", fldPath)
return
}
return
} else {
lastMp, canCast = lastMp[spath].(map[string]interface{})
if !canCast {
err = fmt.Errorf("cannot cast field: %s to map[string]interface{}", ToJSON(lastMp[spath]))
return
}
}
}
err = errors.New("end of function")
return
}
// GetFieldAsString returns the field value as string for the path specified
func (cgrReply CGRReply) GetFieldAsString(fldPath string, sep string) (fldVal string, err error) {
var valIface interface{}
valIface, err = cgrReply.GetField(fldPath, sep)
if err != nil {
return
}
var canCast bool
if fldVal, canCast = CastFieldIfToString(valIface); !canCast {
return "", fmt.Errorf("cannot cast field: %s to string", ToJSON(valIface))
}
return
}

View File

@@ -1,86 +0,0 @@
/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package utils
import (
"errors"
"reflect"
"testing"
)
type myEv map[string]interface{}
func (ev myEv) AsCGRReply() (CGRReply, error) {
return CGRReply(ev), nil
}
func TestCGRReplyNew(t *testing.T) {
eCgrRply := CGRReply(map[string]interface{}{
Error: "some",
})
if rpl, err := NewCGRReply(nil, errors.New("some")); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCgrRply, rpl) {
t.Errorf("Expecting: %+v, received: %+v", ToJSON(eCgrRply), ToJSON(rpl))
}
ev := myEv{
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
"Fld1": "Val1",
},
},
}
eCgrRply = CGRReply(ev)
eCgrRply[Error] = ""
if rpl, err := NewCGRReply(CGRReplier(ev), nil); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCgrRply, rpl) {
t.Errorf("Expecting: %+v, received: %+v", eCgrRply, rpl)
}
}
func TestCGRReplyGetFieldAsString(t *testing.T) {
ev := myEv{
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
"ThirdLevel": map[string]interface{}{
"Fld1": "Val1",
},
},
},
"AnotherFirstLevel": "ValAnotherFirstLevel",
}
cgrRply, _ := NewCGRReply(CGRReplier(ev), nil)
if strVal, err := cgrRply.GetFieldAsString("*cgrReply>Error", ">"); err != nil {
t.Error(err)
} else if strVal != "" {
t.Errorf("received: <%s>", strVal)
}
eVal := "Val1"
if strVal, err := cgrRply.GetFieldAsString("*cgrReply>FirstLevel>SecondLevel>ThirdLevel>Fld1", ">"); err != nil {
t.Error(err)
} else if strVal != eVal {
t.Errorf("expecting: <%s> received: <%s>", eVal, strVal)
}
eVal = "ValAnotherFirstLevel"
if strVal, err := cgrRply.GetFieldAsString("*cgrReply>AnotherFirstLevel", ">"); err != nil {
t.Error(err)
} else if strVal != eVal {
t.Errorf("expecting: <%s> received: <%s>", eVal, strVal)
}
}

View File

@@ -24,6 +24,11 @@ import (
"strings"
)
// CGRReplier is the interface supported by replies convertible to CGRReply
type NavigableMapper interface {
AsNavigableMap() (map[string]interface{}, error)
}
// NavigableMap is a map who's values can be navigated via path
type NavigableMap map[string]interface{}
@@ -53,7 +58,7 @@ func (nM NavigableMap) GetField(fldPath string, sep string) (fldVal interface{},
}
lastMp, canCast = elmnt.(map[string]interface{})
if !canCast {
err = fmt.Errorf("cannot cast field: %s to map[string]interface{}", ToJSON(lastMp[spath]))
err = fmt.Errorf("cannot cast field: %s to map[string]interface{}", ToJSON(elmnt))
return
}
}
@@ -75,3 +80,15 @@ func (nM NavigableMap) GetFieldAsString(fldPath string, sep string) (fldVal stri
}
return
}
// NewCGRReply is specific to replies coming from CGRateS
func NewCGRReply(rply NavigableMapper, errRply error) (nM map[string]interface{}, err error) {
if errRply != nil {
return NavigableMap{Error: errRply.Error()}, nil
}
if nM, err = rply.AsNavigableMap(); err != nil {
return
}
nM[Error] = "" // enforce empty error
return
}

View File

@@ -19,6 +19,7 @@ package utils
import (
"errors"
"reflect"
"testing"
)
@@ -50,3 +51,34 @@ func TestNavMapGetFieldAsString(t *testing.T) {
t.Error(err)
}
}
type myEv map[string]interface{}
func (ev myEv) AsNavigableMap() (map[string]interface{}, error) {
return NavigableMap(ev), nil
}
func TestCGRReplyNew(t *testing.T) {
eCgrRply := map[string]interface{}{
Error: "some",
}
if rpl, err := NewCGRReply(nil, errors.New("some")); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCgrRply, rpl) {
t.Errorf("Expecting: %+v, received: %+v", ToJSON(eCgrRply), ToJSON(rpl))
}
ev := myEv{
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
"Fld1": "Val1",
},
},
}
eCgrRply = ev
eCgrRply[Error] = ""
if rpl, err := NewCGRReply(NavigableMapper(ev), nil); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCgrRply, rpl) {
t.Errorf("Expecting: %+v, received: %+v", eCgrRply, rpl)
}
}