From dde4991d4e5d9acc5740f00f199adf310de9c4b7 Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 8 Jun 2018 16:44:19 +0200 Subject: [PATCH] DiameterAgent forcing usage 0 when debit not possible, replacing CGRReply with NavigableMap --- agents/dmtagent.go | 7 ++- agents/libdmt_test.go | 10 +++- agents/librad.go | 18 +------- agents/librad_test.go | 2 +- agents/radagent.go | 4 +- sessions/sessions.go | 8 ++-- utils/cgrreply.go | 94 -------------------------------------- utils/cgrreply_test.go | 86 ---------------------------------- utils/navigablemap.go | 19 +++++++- utils/navigablemap_test.go | 32 +++++++++++++ 10 files changed, 72 insertions(+), 208 deletions(-) delete mode 100644 utils/cgrreply.go delete mode 100644 utils/cgrreply_test.go diff --git a/agents/dmtagent.go b/agents/dmtagent.go index 778197cdb..641191800 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -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()} } } } diff --git a/agents/libdmt_test.go b/agents/libdmt_test.go index 49b075cc3..e945e2456 100644 --- a/agents/libdmt_test.go +++ b/agents/libdmt_test.go @@ -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") + } } diff --git a/agents/librad.go b/agents/librad.go index 95d51f580..087b1088c 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -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 diff --git a/agents/librad_test.go b/agents/librad_test.go index e3d5d0c9d..c288b719b 100644 --- a/agents/librad_test.go +++ b/agents/librad_test.go @@ -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", diff --git a/agents/radagent.go b/agents/radagent.go index cc44e6e2f..30e8af91d 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -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 { diff --git a/sessions/sessions.go b/sessions/sessions.go index 4e40fd9d4..4e612ea3e 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -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 diff --git a/utils/cgrreply.go b/utils/cgrreply.go deleted file mode 100644 index ae39f05aa..000000000 --- a/utils/cgrreply.go +++ /dev/null @@ -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 -*/ - -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 -} diff --git a/utils/cgrreply_test.go b/utils/cgrreply_test.go deleted file mode 100644 index 8381d479c..000000000 --- a/utils/cgrreply_test.go +++ /dev/null @@ -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 -*/ -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) - } -} diff --git a/utils/navigablemap.go b/utils/navigablemap.go index a6612649a..1884f0533 100644 --- a/utils/navigablemap.go +++ b/utils/navigablemap.go @@ -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 +} diff --git a/utils/navigablemap_test.go b/utils/navigablemap_test.go index 6575ea924..31172cd88 100644 --- a/utils/navigablemap_test.go +++ b/utils/navigablemap_test.go @@ -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) + } +}