refactorings, started groups

This commit is contained in:
Radu Ioan Fericean
2013-08-30 19:02:05 +03:00
parent 637e146ba2
commit 8f804b8bc5
6 changed files with 300 additions and 112 deletions

View File

@@ -64,6 +64,22 @@ func (at *ActionTrigger) Execute(ub *UserBalance) (err error) {
return
}
// returns true if the field of the action timing are equeal to the non empty
// fields of the action
func (at *ActionTrigger) Match(a *Action) bool {
if a == nil {
return true
}
id := a.BalanceId == "" || at.BalanceId == a.BalanceId
direction := a.Direction == "" || at.Direction == a.Direction
thresholdType, thresholdValue := true, true
if a.MinuteBucket != nil {
thresholdType = a.MinuteBucket.PriceType == "" || at.ThresholdType == a.MinuteBucket.PriceType
thresholdValue = a.MinuteBucket.Price == 0 || at.ThresholdValue == a.MinuteBucket.Price
}
return id && direction && thresholdType && thresholdValue
}
// Structure to store actions according to weight
type ActionTriggerPriotityList []*ActionTrigger

View File

@@ -400,6 +400,110 @@ func TestActionTimingPriotityListWeight(t *testing.T) {
}
}
func TestActionTriggerMatchNil(t *testing.T) {
at := &ActionTrigger{
Direction: OUTBOUND,
BalanceId: CREDIT,
ThresholdType: TRIGGER_MAX_BALANCE,
ThresholdValue: 2,
}
var a *Action
if !at.Match(a) {
t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
}
}
func TestActionTriggerMatchAllBlank(t *testing.T) {
at := &ActionTrigger{
Direction: OUTBOUND,
BalanceId: CREDIT,
ThresholdType: TRIGGER_MAX_BALANCE,
ThresholdValue: 2,
}
a := &Action{}
if !at.Match(a) {
t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
}
}
func TestActionTriggerMatchMinuteBucketBlank(t *testing.T) {
at := &ActionTrigger{
Direction: OUTBOUND,
BalanceId: CREDIT,
ThresholdType: TRIGGER_MAX_BALANCE,
ThresholdValue: 2,
}
a := &Action{Direction: OUTBOUND, BalanceId: CREDIT}
if !at.Match(a) {
t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
}
}
func TestActionTriggerMatchMinuteBucketFull(t *testing.T) {
at := &ActionTrigger{
Direction: OUTBOUND,
BalanceId: CREDIT,
ThresholdType: TRIGGER_MAX_BALANCE,
ThresholdValue: 2,
}
a := &Action{MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}}
if !at.Match(a) {
t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
}
}
func TestActionTriggerMatchAllFull(t *testing.T) {
at := &ActionTrigger{
Direction: OUTBOUND,
BalanceId: CREDIT,
ThresholdType: TRIGGER_MAX_BALANCE,
ThresholdValue: 2,
}
a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}}
if !at.Match(a) {
t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
}
}
func TestActionTriggerMatchSomeFalse(t *testing.T) {
at := &ActionTrigger{
Direction: OUTBOUND,
BalanceId: CREDIT,
ThresholdType: TRIGGER_MAX_BALANCE,
ThresholdValue: 2,
}
a := &Action{Direction: INBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}}
if at.Match(a) {
t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
}
}
func TestActionTriggerMatcMinuteBucketFalse(t *testing.T) {
at := &ActionTrigger{
Direction: OUTBOUND,
BalanceId: CREDIT,
ThresholdType: TRIGGER_MAX_BALANCE,
ThresholdValue: 2,
}
a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 3}}
if at.Match(a) {
t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
}
}
func TestActionTriggerMatcAllFalse(t *testing.T) {
at := &ActionTrigger{
Direction: OUTBOUND,
BalanceId: CREDIT,
ThresholdType: TRIGGER_MAX_BALANCE,
ThresholdValue: 2,
}
a := &Action{Direction: INBOUND, BalanceId: MINUTES, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_COUNTER, Price: 3}}
if at.Match(a) {
t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
}
}
func TestActionTriggerPriotityList(t *testing.T) {
at1 := &ActionTrigger{Weight: 10}
at2 := &ActionTrigger{Weight: 20}

119
engine/balances.go Normal file
View File

@@ -0,0 +1,119 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
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 engine
import (
"sort"
"time"
)
type Balance struct {
Id string
Value float64
ExpirationDate time.Time
Weight float64
GroupIds []string
SpecialPercent float64
}
func (b *Balance) Equal(o *Balance) bool {
return b.ExpirationDate.Equal(o.ExpirationDate) ||
b.Weight == o.Weight
}
func (b *Balance) IsExpired() bool {
return !b.ExpirationDate.IsZero() && b.ExpirationDate.Before(time.Now())
}
func (b *Balance) Clone() *Balance {
return &Balance{
Id: b.Id,
Value: b.Value,
ExpirationDate: b.ExpirationDate,
Weight: b.Weight,
}
}
/*
Structure to store minute buckets according to weight, precision or price.
*/
type BalanceChain []*Balance
func (bc BalanceChain) Len() int {
return len(bc)
}
func (bc BalanceChain) Swap(i, j int) {
bc[i], bc[j] = bc[j], bc[i]
}
func (bc BalanceChain) Less(j, i int) bool {
return bc[i].Weight < bc[j].Weight
}
func (bc BalanceChain) Sort() {
sort.Sort(bc)
}
func (bc BalanceChain) GetTotalValue() (total float64) {
for _, b := range bc {
if !b.IsExpired() {
total += b.Value
}
}
return
}
func (bc BalanceChain) Debit(amount float64) float64 {
bc.Sort()
for i, b := range bc {
if b.IsExpired() {
continue
}
if b.Value >= amount || i == len(bc)-1 { // if last one go negative
b.Value -= amount
break
}
b.Value = 0
amount -= b.Value
}
return bc.GetTotalValue()
}
func (bc BalanceChain) Equal(o BalanceChain) bool {
if len(bc) != len(o) {
return false
}
bc.Sort()
o.Sort()
for i := 0; i < len(bc); i++ {
if !bc[i].Equal(o[i]) {
return false
}
}
return true
}
func (bc BalanceChain) Clone() BalanceChain {
var newChain BalanceChain
for _, b := range bc {
newChain = append(newChain, b.Clone())
}
return newChain
}

View File

@@ -369,7 +369,9 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) {
}
for _, ts := range cc.Timespans {
if ts.MinuteInfo != nil {
userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination, true)
if err = userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination, true); err != nil {
return cc, err
}
}
}
}
@@ -387,8 +389,7 @@ func (cd *CallDescriptor) MaxDebit(startTime time.Time) (cc *CallCost, err error
return new(CallCost), errors.New("no more credit")
}
if remainingSeconds > 0 { // for postpaying client returns -1
rs, _ := time.ParseDuration(fmt.Sprintf("%vs", remainingSeconds))
cd.TimeEnd = cd.TimeStart.Add(rs)
cd.TimeEnd = cd.TimeStart.Add(time.Duration(remainingSeconds) * time.Second)
}
return cd.Debit()
}

46
engine/groups.go Normal file
View File

@@ -0,0 +1,46 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
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 engine
import (
"sort"
)
type GroupLink struct {
Id string
Weight float64
}
type GroupLinks []*GroupLink
func (gls GroupLinks) Len() int {
return len(gls)
}
func (gls GroupLinks) Swap(i, j int) {
gls[i], gls[j] = gls[j], gls[i]
}
func (gls GroupLinks) Less(j, i int) bool {
return gls[i].Weight < gls[j].Weight
}
func (gls GroupLinks) Sort() {
sort.Sort(gls)
}

View File

@@ -21,9 +21,7 @@ package engine
import (
"errors"
"github.com/cgrates/cgrates/utils"
"sort"
"strings"
"time"
)
const (
@@ -41,6 +39,11 @@ const (
// action price type
PRICE_PERCENT = "*percent"
PRICE_ABSOLUTE = "*absolute"
// action trigger threshold types
TRIGGER_MIN_COUNTER = "*min_counter"
TRIGGER_MAX_COUNTER = "*max_counter"
TRIGGER_MIN_BALANCE = "*min_balance"
TRIGGER_MAX_BALANCE = "*max_balance"
)
var (
@@ -49,6 +52,7 @@ var (
/*
Structure containing information about user's credit (minutes, cents, sms...).'
This can represent a user or a shared group.
*/
type UserBalance struct {
Id string
@@ -57,104 +61,10 @@ type UserBalance struct {
MinuteBuckets []*MinuteBucket
UnitCounters []*UnitsCounter
ActionTriggers ActionTriggerPriotityList
Groups GroupLinks // user info about groups
// group information
GroupIds []string
UserIds []string
}
type Balance struct {
Id string
Value float64
ExpirationDate time.Time
Weight float64
GroupIds []string
Percent float64
}
func (b *Balance) Equal(o *Balance) bool {
return b.ExpirationDate.Equal(o.ExpirationDate) ||
b.Weight == o.Weight
}
func (b *Balance) IsExpired() bool {
return !b.ExpirationDate.IsZero() && b.ExpirationDate.Before(time.Now())
}
func (b *Balance) Clone() *Balance {
return &Balance{
Id: b.Id,
Value: b.Value,
ExpirationDate: b.ExpirationDate,
Weight: b.Weight,
}
}
/*
Structure to store minute buckets according to weight, precision or price.
*/
type BalanceChain []*Balance
func (bc BalanceChain) Len() int {
return len(bc)
}
func (bc BalanceChain) Swap(i, j int) {
bc[i], bc[j] = bc[j], bc[i]
}
func (bc BalanceChain) Less(j, i int) bool {
return bc[i].Weight < bc[j].Weight
}
func (bc BalanceChain) Sort() {
sort.Sort(bc)
}
func (bc BalanceChain) GetTotalValue() (total float64) {
for _, b := range bc {
if !b.IsExpired() {
total += b.Value
}
}
return
}
func (bc BalanceChain) Debit(amount float64) float64 {
bc.Sort()
for i, b := range bc {
if b.IsExpired() {
continue
}
if b.Value >= amount || i == len(bc)-1 { // if last one go negative
b.Value -= amount
break
}
b.Value = 0
amount -= b.Value
}
return bc.GetTotalValue()
}
func (bc BalanceChain) Equal(o BalanceChain) bool {
if len(bc) != len(o) {
return false
}
bc.Sort()
o.Sort()
for i := 0; i < len(bc); i++ {
if !bc[i].Equal(o[i]) {
return false
}
}
return true
}
func (bc BalanceChain) Clone() BalanceChain {
var newChain BalanceChain
for _, b := range bc {
newChain = append(newChain, b.Clone())
}
return newChain
UserIds []string // group info about users
}
/*
@@ -309,11 +219,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) {
// the next reset (see RESET_TRIGGERS action type)
continue
}
if a != nil && (at.BalanceId != a.BalanceId ||
at.Direction != a.Direction ||
(a.MinuteBucket != nil &&
(at.ThresholdType != a.MinuteBucket.PriceType ||
at.ThresholdValue != a.MinuteBucket.Price))) {
if !at.Match(a) {
continue
}
if strings.Contains(at.ThresholdType, "counter") {
@@ -386,11 +292,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) {
// If the action is not nil it acts like a filter
func (ub *UserBalance) resetActionTriggers(a *Action) {
for _, at := range ub.ActionTriggers {
if a != nil && (at.BalanceId != a.BalanceId ||
at.Direction != a.Direction ||
(a.MinuteBucket != nil &&
(at.ThresholdType != a.MinuteBucket.PriceType ||
at.ThresholdValue != a.MinuteBucket.Price))) {
if !at.Match(a) {
continue
}
at.Executed = false