Add new filter type "*accounts"

This commit is contained in:
TeoV
2019-08-01 16:56:36 +03:00
committed by Dan Christian Bogos
parent cae07d1e35
commit 6e3960fb7a
12 changed files with 301 additions and 34 deletions

View File

@@ -1116,6 +1116,29 @@ func (acnt *Account) Publish() {
}
}
func (acnt *Account) AsNavigableMap(_ []*config.FCTemplate) (*config.NavigableMap, error) {
mpIface := map[string]interface{}{
"ID": acnt.ID,
//"UnitCounters": acnt.UnitCounters,
"ActionTriggers": acnt.ActionTriggers,
"AllowNegative": acnt.AllowNegative,
"Disabled": acnt.Disabled,
}
balanceMap := make(map[string]interface{}, len(acnt.BalanceMap))
for key, balances := range acnt.BalanceMap {
balSls := make([]*config.NavigableMap, len(balances))
for i, balance := range balances {
balSls[i], _ = balance.AsNavigableMap(nil)
}
balanceMap[key] = balSls
}
mpIface["BalanceMap"] = balanceMap
return config.NewNavigableMap(mpIface), nil
}
func NewAccountSummaryFromJSON(jsn string) (acntSummary *AccountSummary, err error) {
if !utils.IsSliceMember([]string{"", "null"}, jsn) { // Unmarshal only when content
json.Unmarshal([]byte(jsn), &acntSummary)

View File

@@ -2279,6 +2279,54 @@ func TestAccountGetMultipleBalancesForPrefixWithSameWeight(t *testing.T) {
}
}
func TestAccountAsNavigableMap(t *testing.T) {
acc := &Account{
BalanceMap: map[string]Balances{
utils.MONETARY: Balances{
&Balance{
ID: "SpecialBalance1",
Value: 10,
Weight: 10.0,
},
&Balance{
ID: "SpecialBalance2",
Value: 10,
Weight: 10.0,
},
},
},
}
nM, _ := acc.AsNavigableMap(nil)
eVal := "SpecialBalance1"
if strVal, err := nM.FieldAsString(
[]string{"BalanceMap", "*monetary[0]", "ID"}); err != nil {
t.Error(err)
} else if strVal != eVal {
t.Errorf("expecting: <%+v> received: <%+v>", eVal, strVal)
}
eVal = "10"
if strVal, err := nM.FieldAsString(
[]string{"BalanceMap", "*monetary[0]", "Value"}); err != nil {
t.Error(err)
} else if strVal != eVal {
t.Errorf("expecting: <%+v> received: <%+v>", eVal, strVal)
}
eVal = "10"
if strVal, err := nM.FieldAsString(
[]string{"BalanceMap", "*monetary[0]", "Weight"}); err != nil {
t.Error(err)
} else if strVal != eVal {
t.Errorf("expecting: <%+v> received: <%+v>", eVal, strVal)
}
eVal = "SpecialBalance2"
if strVal, err := nM.FieldAsString(
[]string{"BalanceMap", "*monetary[1]", "ID"}); err != nil {
t.Error(err)
} else if strVal != eVal {
t.Errorf("expecting: <%+v> received: <%+v>", eVal, strVal)
}
}
/*********************************** Benchmarks *******************************/
func BenchmarkGetSecondForPrefix(b *testing.B) {

View File

@@ -185,6 +185,25 @@ func (b *Balance) Clone() *Balance {
return n
}
func (b *Balance) AsNavigableMap(_ []*config.FCTemplate) (*config.NavigableMap, error) {
return config.NewNavigableMap(map[string]interface{}{
"Uuid": b.Uuid,
"ID": b.ID,
"Value": b.Value,
"ExpirationDate": b.ExpirationDate,
"Weight": b.Weight,
"DestinationIDs": b.DestinationIDs,
"RatingSubject": b.RatingSubject,
"Categories": b.Categories,
"SharedGroups": b.SharedGroups,
"Timings": b.Timings,
"TimingIDs": b.TimingIDs,
"Disabled": b.Disabled,
"Factor": b.Factor,
"Blocker": b.Blocker,
}), nil
}
func (b *Balance) getMatchingPrefixAndDestID(dest string) (prefix, destId string) {
if len(b.DestinationIDs) != 0 && b.DestinationIDs[utils.ANY] == false {
for _, p := range utils.SplitPrefix(dest, MIN_PREFIX_MATCH) {

View File

@@ -62,7 +62,7 @@ const (
)
func NewFilterS(cfg *config.CGRConfig,
statSChan, resSChan chan rpcclient.RpcClientConnection, dm *DataManager) (fS *FilterS) {
statSChan, resSChan, ralSChan chan rpcclient.RpcClientConnection, dm *DataManager) (fS *FilterS) {
fS = &FilterS{
dm: dm,
cfg: cfg,
@@ -73,16 +73,19 @@ func NewFilterS(cfg *config.CGRConfig,
if len(cfg.FilterSCfg().ResourceSConns) != 0 {
fS.connResourceS(resSChan)
}
if len(cfg.FilterSCfg().RALsConns) != 0 {
fS.connRALs(ralSChan)
}
return
}
// FilterS is a service used to take decisions in case of filters
// uses lazy connections where necessary to avoid deadlocks on service startup
type FilterS struct {
cfg *config.CGRConfig
statSConns, resSConns rpcclient.RpcClientConnection
sSConnMux, rSConnMux sync.RWMutex // make sure only one goroutine attempts connecting
dm *DataManager
cfg *config.CGRConfig
statSConns, resSConns, ralSConns rpcclient.RpcClientConnection
sSConnMux, rSConnMux, ralSConnMux sync.RWMutex // make sure only one goroutine attempts connecting
dm *DataManager
}
// connStatS returns will connect towards StatS
@@ -117,6 +120,22 @@ func (fS *FilterS) connResourceS(resSChan chan rpcclient.RpcClientConnection) (e
return
}
// connRALs returns will connect towards RALs
func (fS *FilterS) connRALs(ralSChan chan rpcclient.RpcClientConnection) (err error) {
fS.ralSConnMux.Lock()
defer fS.ralSConnMux.Unlock()
if fS.ralSConns != nil { // connection was populated between locks
return
}
fS.ralSConns, err = NewRPCPool(rpcclient.POOL_FIRST,
fS.cfg.TlsCfg().ClientKey, fS.cfg.TlsCfg().ClientCerificate,
fS.cfg.TlsCfg().CaCertificate, fS.cfg.GeneralCfg().ConnectAttempts,
fS.cfg.GeneralCfg().Reconnects, fS.cfg.GeneralCfg().ConnectTimeout,
fS.cfg.GeneralCfg().ReplyTimeout, fS.cfg.FilterSCfg().RALsConns,
ralSChan, true)
return
}
// Pass will check all filters wihin filterIDs and require them passing for dataProvider
// there should be at least one filter passing, ie: if filters are not active event will fail to pass
// receives the event as DataProvider so we can accept undecoded data (ie: HttpRequest)
@@ -242,6 +261,7 @@ type FilterRule struct {
negative *bool
statItems []*itemFilter // Cached compiled itemFilter out of Values
resourceItems []*itemFilter // Cached compiled itemFilter out of Values
accountItems []*itemFilter // Cached compiled itemFilter out of Values
}
// Separate method to compile RSR fields
@@ -279,7 +299,7 @@ func (rf *FilterRule) CompileValues() (err error) {
return fmt.Errorf("Value %s needs to contain at least 3 items", val)
}
// valSplt[0] filter type
// valSplt[1] id of the Resource
// valSplt[1] id of the AccountID.FieldToUsed
// valSplt[2] value to compare
rf.resourceItems[i] = &itemFilter{
FilterType: valSplt[0],
@@ -287,6 +307,24 @@ func (rf *FilterRule) CompileValues() (err error) {
FilterValue: valSplt[2],
}
}
case utils.MetaAccounts:
//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
}
@@ -323,6 +361,8 @@ func (fltr *FilterRule) Pass(dP config.DataProvider,
result, err = fltr.passResourceS(dP, rpcClnt, tenant)
case MetaEqual, MetaNotEqual:
result, err = fltr.passEqualTo(dP)
case utils.MetaAccounts:
result, err = fltr.passAccountS(dP, rpcClnt, tenant)
default:
err = utils.ErrPrefixNotErrNotImplemented(fltr.Type)
}
@@ -580,6 +620,38 @@ func (fltr *FilterRule) passResourceS(dP config.DataProvider,
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(dP config.DataProvider) (bool, error) {
fldIf, err := config.GetDynamicInterface(fltr.FieldName, dP)
if err != nil {