Remove *account filter type and add is as a DataProvider

This commit is contained in:
TeoV
2019-09-06 17:33:44 +03:00
committed by Dan Christian Bogos
parent 10ebf4c4c6
commit 7e0ae18009
6 changed files with 220 additions and 109 deletions

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package config
import (
"fmt"
"net"
"strings"
@@ -52,13 +53,22 @@ func GetDynamicString(dnVal string, dP DataProvider) (string, error) {
//NewObjectDP constructs a DataProvider
func NewObjectDP(obj interface{}) (dP DataProvider) {
dP = &ObjectDP{obj: obj, cache: NewNavigableMap(nil)}
dP = &ObjectDP{obj: obj, cache: make(map[string]interface{})}
return
}
type ObjectDP struct {
obj interface{}
cache *NavigableMap
cache map[string]interface{}
}
func (objDp *ObjectDP) setCache(path string, val interface{}) {
objDp.cache[path] = val
}
func (objDp *ObjectDP) getCache(path string) (val interface{}, has bool) {
val, has = objDp.cache[path]
return
}
// String is part of engine.DataProvider interface
@@ -70,16 +80,59 @@ func (objDP *ObjectDP) String() string {
// FieldAsInterface is part of engine.DataProvider interface
func (objDP *ObjectDP) FieldAsInterface(fldPath []string) (data interface{}, err error) {
// []string{ BalanceMap *monetary[0] Value }
if data, err = objDP.cache.FieldAsInterface(fldPath); err == nil ||
err != utils.ErrNotFound { // item found in cache
var has bool
if data, has = objDP.getCache(strings.Join(fldPath, ".")); has {
return
}
err = nil // cancel previous err
// for _, fld := range fldPath {
// //process each field
// }
objDP.cache.Set(fldPath, data, false, false)
var prevFld string
for _, fld := range fldPath {
var slctrStr string
if splt := strings.Split(fld, "["); len(splt) != 1 { // check if we have selector
fld = splt[0]
if splt[1][len(splt[1])-1:] != "]" {
return nil, fmt.Errorf("filter rule <%s> needs to end in ]", splt[1])
}
slctrStr = splt[1][:len(splt[1])-1] // also strip the last ]
}
if prevFld == utils.EmptyString {
prevFld += fld
} else {
prevFld += "." + fld
}
// check if we take the current path from cache
if data, has = objDP.getCache(prevFld); !has {
if data, err = utils.ReflectFieldMethodInterface(objDP.obj, fld); err != nil { // take the object the field for current path
// in case of error set nil for the current path and return err
objDP.setCache(prevFld, nil)
return nil, err
}
// add the current field in prevFld so we can set in cache the full path with it's data
objDP.setCache(prevFld, data)
}
// change the obj to be the current data and continue the processing
objDP.obj = data
if slctrStr != utils.EmptyString { //we have selector so we need to do an aditional get
prevFld += "[" + slctrStr + "]"
// check if we take the current path from cache
if data, has = objDP.getCache(prevFld); !has {
if data, err = utils.ReflectFieldMethodInterface(objDP.obj, slctrStr); err != nil { // take the object the field for current path
// in case of error set nil for the current path and return err
objDP.setCache(prevFld, nil)
return nil, err
}
// add the current field in prevFld so we can set in cache the full path with it's data
objDP.setCache(prevFld, data)
}
// change the obj to be the current data and continue the processing
objDP.obj = data
}
}
//add in cache the initial path
objDP.setCache(strings.Join(fldPath, "."), data)
return
}

View File

@@ -126,7 +126,7 @@ func (fS *FilterS) Pass(tenant string, filterIDs []string,
continue
}
for _, fltr := range f.Rules {
fieldNameDP, err = fS.getFieldNameDataProvider(ev, fltr.FieldName, tenant)
fieldNameDP, err = fS.getFieldNameDataProvider(ev, &fltr.FieldName, tenant)
if err != nil {
return pass, err
}
@@ -186,10 +186,10 @@ func (f *Filter) Compile() (err error) {
}
var supportedFiltersType *utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix, utils.MetaSuffix,
utils.MetaTimings, utils.MetaRSR, utils.MetaStatS, utils.MetaDestinations,
utils.MetaTimings, utils.MetaRSR, utils.MetaDestinations,
utils.MetaEmpty, utils.MetaExists, utils.MetaLessThan, utils.MetaLessOrEqual,
utils.MetaGreaterThan, utils.MetaGreaterOrEqual, utils.MetaResources, utils.MetaEqual,
utils.MetaAccount, utils.MetaNotEqual})
utils.MetaGreaterThan, utils.MetaGreaterOrEqual, utils.MetaEqual,
utils.MetaNotEqual})
var needsFieldName *utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix,
utils.MetaSuffix, utils.MetaTimings, utils.MetaDestinations, utils.MetaLessThan,
utils.MetaEmpty, utils.MetaExists, utils.MetaLessOrEqual, utils.MetaGreaterThan,
@@ -290,24 +290,6 @@ func (rf *FilterRule) CompileValues() (err error) {
FilterValue: valSplt[2],
}
}
case utils.MetaAccount:
//value for filter of type *accounts needs to be in the following form:
//*gt:AccountID:ValueOfUsage
rf.accountItems = make([]*itemFilter, len(rf.Values))
for i, val := range rf.Values {
valSplt := strings.Split(val, utils.InInFieldSep)
if len(valSplt) != 3 {
return fmt.Errorf("Value %s needs to contain at least 3 items", val)
}
// valSplt[0] filter type
// valSplt[1] id of the Resource
// valSplt[2] value to compare
rf.accountItems[i] = &itemFilter{
FilterType: valSplt[0],
ItemID: valSplt[1],
FilterValue: valSplt[2],
}
}
}
return
}
@@ -597,38 +579,6 @@ func (fltr *FilterRule) passGreaterThan(fielNameDP config.DataProvider, fieldVal
// return true, nil
// }
// func (fltr *FilterRule) passAccountS(dP config.DataProvider,
// accountS rpcclient.RpcClientConnection, tenant string) (bool, error) {
// if accountS == nil || reflect.ValueOf(accountS).IsNil() {
// return false, errors.New("Missing AccountS information")
// }
// for _, accItem := range fltr.accountItems {
// //split accItem.ItemID in two accountID and actual filter
// //AccountID.BalanceMap.*monetary[0].Value
// splittedString := strings.SplitN(accItem.ItemID, utils.NestingSep, 2)
// accID := splittedString[0]
// filterID := splittedString[1]
// var reply Account
// if err := accountS.Call(utils.ApierV2GetAccount,
// &utils.AttrGetAccount{Tenant: tenant, Account: accID}, &reply); err != nil {
// return false, err
// }
// //compose the newFilter
// fltr, err := NewFilterRule(accItem.FilterType,
// utils.DynamicDataPrefix+filterID, []string{accItem.FilterValue})
// if err != nil {
// return false, err
// }
// dP, _ := reply.AsNavigableMap(nil)
// if val, err := fltr.Pass(dP, nil, tenant); err != nil || !val {
// //in case of error return false and error
// //and in case of not pass return false and nil
// return false, err
// }
// }
// return true, nil
// }
func (fltr *FilterRule) passEqualTo(fielNameDP config.DataProvider, fieldValuesDP []config.DataProvider) (bool, error) {
fldIf, err := config.GetDynamicInterface(fltr.FieldName, fielNameDP)
if err != nil {
@@ -654,41 +604,39 @@ func (fltr *FilterRule) passEqualTo(fielNameDP config.DataProvider, fieldValuesD
return false, nil
}
func (fS *FilterS) getFieldNameDataProvider(initialDP config.DataProvider, fieldName string, tenant string) (dp config.DataProvider, err error) {
func (fS *FilterS) getFieldNameDataProvider(initialDP config.DataProvider, fieldName *string, tenant string) (dp config.DataProvider, err error) {
switch {
case strings.HasPrefix(fieldName, utils.MetaAccounts):
//construct dataProvider from account and set it furthder
case strings.HasPrefix(*fieldName, utils.DynamicDataPrefix+utils.MetaAccounts):
//same of fieldName : ~*accounts.1001.BalanceMap.*monetary[0].Value
// split the field name in 3 parts
// fieldNameType (~*accounts), accountID(1001) and quried part (BalanceMap.*monetary[0].Value)
splitFldName := strings.SplitN(*fieldName, ".", 3)
if len(splitFldName) != 3 {
return nil, fmt.Errorf("invalid fieldname <%s>", *fieldName)
}
var account *Account
//extract the AccountID from fieldName
if err = fS.ralSConns.Call(utils.ApierV2GetAccount,
&utils.AttrGetAccount{Tenant: tenant, Account: "completeHereWithID"}, &account); err != nil {
&utils.AttrGetAccount{Tenant: tenant, Account: splitFldName[1]}, &account); err != nil {
return
}
//construct dataProvider from account and set it furthder
dp = config.NewObjectDP(account)
case strings.HasPrefix(fieldName, utils.MetaResources):
case strings.HasPrefix(fieldName, utils.MetaStats):
// remove from fieldname the fielNameType and the AccountID
*fieldName = utils.DynamicDataPrefix + splitFldName[2]
case strings.HasPrefix(*fieldName, utils.DynamicDataPrefix+utils.MetaResources):
case strings.HasPrefix(*fieldName, utils.DynamicDataPrefix+utils.MetaStats):
default:
dp = initialDP
}
return
}
func (fS *FilterS) getFieldValueDataProviders(initialDP config.DataProvider, values []string, tenant string) (dp []config.DataProvider, err error) {
func (fS *FilterS) getFieldValueDataProviders(initialDP config.DataProvider,
values []string, tenant string) (dp []config.DataProvider, err error) {
dp = make([]config.DataProvider, len(values))
for i, val := range values {
switch {
case strings.HasPrefix(val, utils.MetaAccounts):
var account *Account
//extract the AccountID from fieldName
if err = fS.ralSConns.Call(utils.ApierV2GetAccount,
&utils.AttrGetAccount{Tenant: tenant, Account: "completeHereWithID"}, &account); err != nil {
return
}
dp[i] = config.NewObjectDP(account)
case strings.HasPrefix(val, utils.MetaResources):
case strings.HasPrefix(val, utils.MetaStats):
default:
dp[i] = initialDP
for i := range values {
if dp[i], err = fS.getFieldNameDataProvider(initialDP, &values[i], tenant); err != nil {
return
}
}
return

View File

@@ -48,11 +48,11 @@ var sTestsFltr = []func(t *testing.T){
testV1FltrStartEngine,
testV1FltrRpcConn,
testV1FltrLoadTarrifPlans,
testV1FltrAddStats,
testV1FltrPupulateThreshold,
testV1FltrGetThresholdForEvent,
testV1FltrGetThresholdForEvent2,
testV1FltrPopulateResources,
//testV1FltrAddStats,
//testV1FltrPupulateThreshold,
//testV1FltrGetThresholdForEvent,
//testV1FltrGetThresholdForEvent2,
//testV1FltrPopulateResources,
testV1FltrAccounts,
testV1FltrStopEngine,
}
@@ -519,15 +519,17 @@ func testV1FltrAccounts(t *testing.T) {
} else if resp != utils.OK {
t.Error("Unexpected reply returned", resp)
}
//Add a filter of type *accounts and check if *monetary balance of account 1001 is minim 9 ( greater than 9)
//we expect that the balance to be 10 so the filter should pass (10 > 9)
// Add a filter with fieldName taken value from account 1001
// and check if *monetary balance is minim 9 ( greater than 9)
// we expect that the balance to be 10 so the filter should pass (10 > 9)
filter := &engine.Filter{
Tenant: "cgrates.org",
ID: "FLTR_TH_Accounts",
Rules: []*engine.FilterRule{
{
Type: "*account",
Values: []string{"*gt:1001.BalanceMap.*monetary[0].Value:9"},
Type: "*gt",
FieldName: "~*accounts.1001.BalanceMap.*monetary[0].Value",
Values: []string{"9"},
},
},
}
@@ -538,7 +540,16 @@ func testV1FltrAccounts(t *testing.T) {
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
// Add a log action
attrsAA := &utils.AttrSetActions{ActionsId: "LOG", Actions: []*utils.TPAction{
{Identifier: utils.LOG},
}}
if err := fltrRpc.Call("ApierV2.SetActions", attrsAA, &result); err != nil && err.Error() != utils.ErrExists.Error() {
t.Error("Got error on ApierV2.SetActions: ", err.Error())
} else if result != utils.OK {
t.Errorf("Calling ApierV2.SetActions received: %s", result)
}
time.Sleep(10 * time.Millisecond)
//Add a threshold with filter from above and an inline filter for Account 1010
tPrfl := &engine.ThresholdProfile{
Tenant: "cgrates.org",
@@ -581,15 +592,17 @@ func testV1FltrAccounts(t *testing.T) {
}
// update the filter
//Add a filter of type *accounts and check if *monetary balance of account 1001 is minim 11 ( greater than 11)
//we expect that the balance to be 10 so the filter should not pass (10 > 11)
// Add a filter with fieldName taken value from account 1001
// and check if *monetary balance is is minim 11 ( greater than 11)
// we expect that the balance to be 10 so the filter should not pass (10 > 11)
filter = &engine.Filter{
Tenant: "cgrates.org",
ID: "FLTR_TH_Accounts",
Rules: []*engine.FilterRule{
{
Type: "*account",
Values: []string{"*gt:1001.BalanceMap.*monetary[0].Value:11"},
Type: "*gt",
FieldName: "~*accounts.1001.BalanceMap.*monetary[0].Value",
Values: []string{"11"},
},
},
}

View File

@@ -0,0 +1,86 @@
/*
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 general_tests
import (
"testing"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
func TestAccountNewObjectDPFieldAsInterface(t *testing.T) {
acc := &engine.Account{
ID: "cgrates.org:1001",
BalanceMap: map[string]engine.Balances{
utils.MONETARY: []*engine.Balance{
{
Value: 20,
Weight: 10,
},
},
},
}
accDP := config.NewObjectDP(acc)
if data, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]", "Value"}); err != nil {
t.Error(err)
} else if data != 20. {
t.Errorf("Expected: %+v ,recived: %+v", 20., data)
}
if _, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[1]", "Value"}); err == nil ||
err.Error() != "index out of range" {
t.Error(err)
}
if _, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]", "InexistentField"}); err == nil ||
err != utils.ErrNotFound {
t.Error(err)
}
}
func TestAccountNewObjectDPFieldAsInterfaceFromCache(t *testing.T) {
acc := &engine.Account{
ID: "cgrates.org:1001",
BalanceMap: map[string]engine.Balances{
utils.MONETARY: []*engine.Balance{
{
Value: 20,
Weight: 10,
},
},
},
}
accDP := config.NewObjectDP(acc)
if data, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]", "Value"}); err != nil {
t.Error(err)
} else if data != 20. {
t.Errorf("Expected: %+v ,recived: %+v", 20., data)
}
// the value should be taken from cache
if data, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]", "Value"}); err != nil {
t.Error(err)
} else if data != 20. {
t.Errorf("Expected: %+v ,recived: %+v", 20., data)
}
if data, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]"}); err != nil {
t.Error(err)
} else if data != acc.BalanceMap[utils.MONETARY][0] {
t.Errorf("Expected: %+v ,recived: %+v", acc.BalanceMap[utils.MONETARY][0], data)
}
}

View File

@@ -562,8 +562,8 @@ func ReflectFieldMethodInterface(obj interface{}, fldName string) (retIf interfa
if err != nil {
return nil, err
}
if idx > v.Len() {
return nil, fmt.Errorf("out of range")
if idx > v.Len()-1 {
return nil, fmt.Errorf("index out of range")
}
field = v.Index(idx)
default:
@@ -582,12 +582,17 @@ func ReflectFieldMethodInterface(obj interface{}, fldName string) (retIf interfa
if field.Type().NumOut() > 2 {
return nil, fmt.Errorf("invalid function called")
}
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
if !field.Type().Out(1).Implements(errorInterface) {
return nil, fmt.Errorf("invalid function called")
// the function have two parameters in return and check if the second is of type error
if field.Type().NumOut() == 2 {
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
if !field.Type().Out(1).Implements(errorInterface) {
return nil, fmt.Errorf("invalid function called")
}
}
fields := field.Call([]reflect.Value{})
//verify if error is not nil
if len(fields) == 2 && !fields[1].IsNil() {
return fields[0].Interface(), fields[1].Interface().(error)
}
return fields[0].Interface(), nil
}
}

View File

@@ -699,6 +699,8 @@ type TestA struct {
StrField string
}
type TestASlice []*TestA
func (_ *TestA) TestFunc() string {
return "This is a test function on a structure"
}
@@ -708,10 +710,10 @@ func (_ *TestA) TestFuncWithParam(param string) string {
}
func (_ *TestA) TestFuncWithError() (string, error) {
return "TestFunction", nil
return "TestFuncWithError", nil
}
func (_ *TestA) TestFuncWithError2() (string, error) {
return "TestFunction", ErrPartiallyExecuted
return "TestFuncWithError2", ErrPartiallyExecuted
}
func TestReflectFieldMethodInterface(t *testing.T) {
@@ -735,7 +737,11 @@ func TestReflectFieldMethodInterface(t *testing.T) {
ifValue, err = ReflectFieldMethodInterface(a, "TestFuncWithError")
if err != nil {
t.Error(err)
} else if ifValue != "TestFunction" {
t.Errorf("Expecting: TestFunction, received: %+v", ifValue)
} else if ifValue != "TestFuncWithError" {
t.Errorf("Expecting: TestFuncWithError, received: %+v", ifValue)
}
ifValue, err = ReflectFieldMethodInterface(a, "TestFuncWithError2")
if err == nil || err != ErrPartiallyExecuted {
t.Error(err)
}
}