Merge branch 'master' into load

This commit is contained in:
Radu Ioan Fericean
2015-05-25 16:31:18 +03:00
30 changed files with 1035 additions and 291 deletions

View File

@@ -915,6 +915,7 @@ func (self *ApierV1) GetCacheStats(attrs utils.AttrCacheStats, reply *utils.Cach
cs.RatingAliases = cache2go.CountEntries(engine.RP_ALIAS_PREFIX)
cs.AccountAliases = cache2go.CountEntries(engine.ACC_ALIAS_PREFIX)
cs.DerivedChargers = cache2go.CountEntries(engine.DERIVEDCHARGERS_PREFIX)
cs.LcrProfiles = cache2go.CountEntries(engine.LCR_PREFIX)
*reply = *cs
return nil
}
@@ -926,7 +927,8 @@ func (self *ApierV1) GetCachedItemAge(itemId string, reply *utils.CachedItemAge)
cachedItemAge := new(utils.CachedItemAge)
var found bool
for idx, cacheKey := range []string{engine.DESTINATION_PREFIX + itemId, engine.RATING_PLAN_PREFIX + itemId, engine.RATING_PROFILE_PREFIX + itemId,
engine.ACTION_PREFIX + itemId, engine.SHARED_GROUP_PREFIX + itemId, engine.RP_ALIAS_PREFIX + itemId, engine.ACC_ALIAS_PREFIX + itemId} {
engine.ACTION_PREFIX + itemId, engine.SHARED_GROUP_PREFIX + itemId, engine.RP_ALIAS_PREFIX + itemId, engine.ACC_ALIAS_PREFIX + itemId,
engine.LCR_PREFIX + itemId} {
if age, err := cache2go.GetKeyAge(cacheKey); err == nil {
found = true
switch idx {
@@ -944,6 +946,8 @@ func (self *ApierV1) GetCachedItemAge(itemId string, reply *utils.CachedItemAge)
cachedItemAge.RatingAlias = age
case 6:
cachedItemAge.AccountAlias = age
case 7:
cachedItemAge.LcrProfiles = age
}
}
}

View File

@@ -1745,7 +1745,7 @@ func TestApierGetCacheStats3(t *testing.T) {
return
}
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1}
expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1}
var args utils.AttrCacheStats
if err := rater.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil {
t.Error("Got error on ApierV1.GetCacheStats: ", err.Error())

View File

@@ -21,77 +21,16 @@ package v1
import (
"errors"
"fmt"
"time"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
// A request for LCR
type LcrRequest struct {
Direction string
Tenant string
Category string
Account string
Subject string
Destination string
TimeStart string
Duration string
}
// A LCR reply
type LcrReply struct {
DestinationId string
RPCategory string
Strategy string
Suppliers []*LcrSupplier
}
// One supplier out of LCR reply
type LcrSupplier struct {
Supplier string
Cost float64
}
// Computes the LCR for a specific request emulating a call
func (self *ApierV1) GetLcr(lcrReq LcrRequest, lcrReply *LcrReply) (err error) {
if missing := utils.MissingStructFields(&lcrReq, []string{"Account", "Destination"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
// Handle defaults
if len(lcrReq.Direction) == 0 {
lcrReq.Direction = utils.OUT
}
if len(lcrReq.Tenant) == 0 {
lcrReq.Tenant = self.Config.DefaultTenant
}
if len(lcrReq.Category) == 0 {
lcrReq.Category = self.Config.DefaultCategory
}
if len(lcrReq.Subject) == 0 {
lcrReq.Subject = lcrReq.Account
}
var timeStart time.Time
if len(lcrReq.TimeStart) == 0 {
timeStart = time.Now()
} else if timeStart, err = utils.ParseTimeDetectLayout(lcrReq.TimeStart); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
var callDur time.Duration
if len(lcrReq.Duration) == 0 {
callDur = time.Duration(1) * time.Minute
} else if callDur, err = utils.ParseDurationWithSecs(lcrReq.Duration); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
cd := &engine.CallDescriptor{
Direction: lcrReq.Direction,
Tenant: lcrReq.Tenant,
Category: lcrReq.Category,
Account: lcrReq.Account,
Subject: lcrReq.Subject,
Destination: lcrReq.Destination,
TimeStart: timeStart,
TimeEnd: timeStart.Add(callDur),
func (self *ApierV1) GetLcr(lcrReq engine.LcrRequest, lcrReply *engine.LcrReply) error {
cd, err := lcrReq.AsCallDescriptor()
if err != nil {
return err
}
var lcrQried engine.LCRCost
if err := self.Responder.GetLCR(cd, &lcrQried); err != nil {
@@ -100,6 +39,10 @@ func (self *ApierV1) GetLcr(lcrReq LcrRequest, lcrReply *LcrReply) (err error) {
if lcrQried.Entry == nil {
return errors.New(utils.ERR_NOT_FOUND)
}
if lcrQried.HasErrors() {
lcrQried.LogErrors()
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "LCR_COMPUTE_ERRORS")
}
lcrReply.DestinationId = lcrQried.Entry.DestinationId
lcrReply.RPCategory = lcrQried.Entry.RPCategory
lcrReply.Strategy = lcrQried.Entry.Strategy
@@ -107,68 +50,30 @@ func (self *ApierV1) GetLcr(lcrReq LcrRequest, lcrReply *LcrReply) (err error) {
if dtcs, err := utils.NewDTCSFromRPKey(qriedSuppl.Supplier); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
lcrReply.Suppliers = append(lcrReply.Suppliers, &LcrSupplier{Supplier: dtcs.Subject, Cost: qriedSuppl.Cost})
lcrReply.Suppliers = append(lcrReply.Suppliers, &engine.LcrSupplier{Supplier: dtcs.Subject, Cost: qriedSuppl.Cost, QOS: qriedSuppl.QOS})
}
}
return nil
}
// Computes the LCR for a specific request emulating a call, returns a comma separated list of suppliers
func (self *ApierV1) GetLcrSuppliers(lcrReq LcrRequest, suppliers *string) (err error) {
if missing := utils.MissingStructFields(&lcrReq, []string{"Account", "Destination"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
// Handle defaults
if len(lcrReq.Direction) == 0 {
lcrReq.Direction = utils.OUT
}
if len(lcrReq.Tenant) == 0 {
lcrReq.Tenant = self.Config.DefaultTenant
}
if len(lcrReq.Category) == 0 {
lcrReq.Category = self.Config.DefaultCategory
}
if len(lcrReq.Subject) == 0 {
lcrReq.Subject = lcrReq.Account
}
var timeStart time.Time
if len(lcrReq.TimeStart) == 0 {
timeStart = time.Now()
} else if timeStart, err = utils.ParseTimeDetectLayout(lcrReq.TimeStart); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
var callDur time.Duration
if len(lcrReq.Duration) == 0 {
callDur = time.Duration(1) * time.Minute
} else if callDur, err = utils.ParseDurationWithSecs(lcrReq.Duration); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
cd := &engine.CallDescriptor{
Direction: lcrReq.Direction,
Tenant: lcrReq.Tenant,
Category: lcrReq.Category,
Account: lcrReq.Account,
Subject: lcrReq.Subject,
Destination: lcrReq.Destination,
TimeStart: timeStart,
TimeEnd: timeStart.Add(callDur),
func (self *ApierV1) GetLcrSuppliers(lcrReq engine.LcrRequest, suppliers *string) (err error) {
cd, err := lcrReq.AsCallDescriptor()
if err != nil {
return err
}
var lcrQried engine.LCRCost
if err := self.Responder.GetLCR(cd, &lcrQried); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if lcrQried.Entry == nil {
return errors.New(utils.ERR_NOT_FOUND)
if lcrQried.HasErrors() {
lcrQried.LogErrors()
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "LCR_ERRORS")
}
for idx, qriedSuppl := range lcrQried.SupplierCosts {
if dtcs, err := utils.NewDTCSFromRPKey(qriedSuppl.Supplier); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
if idx != 0 {
*suppliers += ","
}
*suppliers += dtcs.Subject
}
if suppliersStr, err := lcrQried.SuppliersString(); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*suppliers = suppliersStr
}
return nil
}

View File

@@ -18,13 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import "github.com/cgrates/cgrates/apier/v1"
import (
"github.com/cgrates/cgrates/engine"
)
func init() {
c := &CmdGetLcr{
name: "lcr",
rpcMethod: "ApierV1.GetLcr",
rpcParams: &v1.LcrRequest{},
rpcParams: &engine.LcrRequest{},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
@@ -34,7 +36,7 @@ func init() {
type CmdGetLcr struct {
name string
rpcMethod string
rpcParams *v1.LcrRequest
rpcParams *engine.LcrRequest
*CommandExecuter
}
@@ -55,5 +57,5 @@ func (self *CmdGetLcr) PostprocessRpcParams() error {
}
func (self *CmdGetLcr) RpcResult() interface{} {
return &v1.LcrReply{}
return &engine.LcrReply{}
}

View File

@@ -1,3 +0,0 @@
#/usr/bin/env sh
docker run --rm -p 3306:3306 -p 6379:6379 -p 2012:2012 -p 2013:2013 -p 2080:2080 -itv `pwd`:/root/code/src/github.com/cgrates/cgrates --name cgr cgrates

View File

@@ -1,24 +1,24 @@
#Id[0],QueueLength[1],TimeWindow[2],Metric[3],SetupInterval[4],TOR[5],CdrHost[6],CdrSource[7],ReqType[8],Direction[9],Tenant[10],Category[11],Account[12],Subject[13],DestinationPrefix[14],UsageInterval[15],Supplier[16],DisconnectCause[17],MediationRunIds[18],RatedAccount[19],RatedSubject[20],CostInterval[21],Triggers[22]
CDRST1,10,0,ASR,,,,,,,cgrates.org,,,,,,,,default,,,,CDRST1_WARN
CDRST1,10,0,ASR,,,,,,,cgrates.org,,,,,,,,*default,,,,CDRST1_WARN
CDRST1,,,ACD,,,,,,,,,,,,,,,,,,,
CDRST1,,,ACC,,,,,,,,,,,,,,,,,,,
CDRST1,,,TCD,,,,,,,,,,,,,,,,,,,
CDRST1,,,TCC,,,,,,,,,,,,,,,,,,,
CDRST_1001,10,10m,ASR,,,,,,,cgrates.org,,,1001,,,,,default,,,,CDRST1001_WARN
CDRST_1001,10,10m,ASR,,,,,,,cgrates.org,,,1001,,,,,*default,,,,CDRST1001_WARN
CDRST_1001,,,ACD,,,,,,,,,,,,,,,,,,,
CDRST_1001,,,ACC,,,,,,,,,,,,,,,,,,,
CDRST_1002,10,10m,ASR,,,,,,,cgrates.org,,,1002,,,,,default,,,,CDRST1001_WARN
CDRST_1002,10,10m,ASR,,,,,,,cgrates.org,,,1002,,,,,*default,,,,CDRST1001_WARN
CDRST_1002,,,ACD,,,,,,,,,,,,,,,,,,,
CDRST_1002,,,ACC,,,,,,,,,,,,,,,,,,,
CDRST_1003,,,ASR,,,,,,,cgrates.org,,,,1003,,,,default,,,,CDRST3_WARN
CDRST_1003,,,ASR,,,,,,,cgrates.org,,,,1003,,,,*default,,,,CDRST3_WARN
CDRST_1003,,,ACD,,,,,,,,,,,,,,,,,,,
STATS_SUPPL1,,,ACD,,,,,,,,,,,,,supplier1,,,,,,
STATS_SUPPL1,,,ASR,,,,,,,,,,,,,supplier1,,,,,,
STATS_SUPPL1,,,ACC,,,,,,,,,,,,,supplier1,,,,,,
STATS_SUPPL1,,,TCD,,,,,,,,,,,,,supplier1,,,,,,
STATS_SUPPL1,,,TCC,,,,,,,,,,,,,supplier1,,,,,,
STATS_SUPPL2,,,ACD,,,,,,,,,,,,,supplier2,,,,,,
STATS_SUPPL2,,,ASR,,,,,,,,,,,,,supplier2,,,,,,
STATS_SUPPL2,,,ACC,,,,,,,,,,,,,supplier2,,,,,,
STATS_SUPPL2,,,TCD,,,,,,,,,,,,,supplier2,,,,,,
STATS_SUPPL2,,,TCC,,,,,,,,,,,,,supplier2,,,,,,
STATS_SUPPL1,,,ACD,,,,,,,,,,,,,suppl1,,,,,,
STATS_SUPPL1,,,ASR,,,,,,,,,,,,,suppl1,,,,,,
STATS_SUPPL1,,,ACC,,,,,,,,,,,,,suppl1,,,,,,
STATS_SUPPL1,,,TCD,,,,,,,,,,,,,suppl1,,,,,,
STATS_SUPPL1,,,TCC,,,,,,,,,,,,,suppl1,,,,,,
STATS_SUPPL2,,,ACD,,,,,,,,,,,,,suppl2,,,,,,
STATS_SUPPL2,,,ASR,,,,,,,,,,,,,suppl2,,,,,,
STATS_SUPPL2,,,ACC,,,,,,,,,,,,,suppl2,,,,,,
STATS_SUPPL2,,,TCD,,,,,,,,,,,,,suppl2,,,,,,
STATS_SUPPL2,,,TCC,,,,,,,,,,,,,suppl2,,,,,,
1 #Id[0] QueueLength[1] TimeWindow[2] Metric[3] SetupInterval[4] TOR[5] CdrHost[6] CdrSource[7] ReqType[8] Direction[9] Tenant[10] Category[11] Account[12] Subject[13] DestinationPrefix[14] UsageInterval[15] Supplier[16] DisconnectCause[17] MediationRunIds[18] RatedAccount[19] RatedSubject[20] CostInterval[21] Triggers[22]
2 CDRST1 10 0 ASR cgrates.org default *default CDRST1_WARN
3 CDRST1 ACD
4 CDRST1 ACC
5 CDRST1 TCD
6 CDRST1 TCC
7 CDRST_1001 10 10m ASR cgrates.org 1001 default *default CDRST1001_WARN
8 CDRST_1001 ACD
9 CDRST_1001 ACC
10 CDRST_1002 10 10m ASR cgrates.org 1002 default *default CDRST1001_WARN
11 CDRST_1002 ACD
12 CDRST_1002 ACC
13 CDRST_1003 ASR cgrates.org 1003 default *default CDRST3_WARN
14 CDRST_1003 ACD
15 STATS_SUPPL1 ACD supplier1 suppl1
16 STATS_SUPPL1 ASR supplier1 suppl1
17 STATS_SUPPL1 ACC supplier1 suppl1
18 STATS_SUPPL1 TCD supplier1 suppl1
19 STATS_SUPPL1 TCC supplier1 suppl1
20 STATS_SUPPL2 ACD supplier2 suppl2
21 STATS_SUPPL2 ASR supplier2 suppl2
22 STATS_SUPPL2 ACC supplier2 suppl2
23 STATS_SUPPL2 TCD supplier2 suppl2
24 STATS_SUPPL2 TCC supplier2 suppl2

View File

@@ -2,4 +2,3 @@
DST_1002,1002
DST_1003,1003
DST_FS,10
DST_2002,2002
1 #Tag Prefix
2 DST_1002 1002
3 DST_1003 1003
4 DST_FS 10
DST_2002 2002

View File

@@ -1,9 +1,9 @@
#Direction,Tenant,Category,Account,Subject,DestinationId,RPCategory,Strategy,StrategyParams,ActivationTime,Weight
*out,cgrates.org,call,1001,*any,DST_2002,lcr_profile1,*static,suppl2;suppl1,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1001,*any,DST_1002,lcr_profile1,*static,suppl2;suppl1,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1001,*any,*any,lcr_profile1,*static,suppl1;suppl2,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1002,*any,DST_2002,lcr_profile1,*highest_cost,,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1002,*any,DST_1002,lcr_profile1,*highest_cost,,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1002,*any,*any,lcr_profile1,*qos,,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1003,*any,DST_2002,lcr_profile1,*qos_threshold,20;;2m;;;;;;;,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1003,*any,*any,lcr_profile1,*qos_threshold,40;;4m;;;;;;;,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,*any,*any,DST_2002,lcr_profile2,*lowest_cost,,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1003,*any,DST_1002,lcr_profile1,*qos_threshold,20;;2m;;;;;;;,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,1003,*any,*any,lcr_profile1,*qos_threshold,40;;90s;;;;;;;,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,*any,*any,DST_1002,lcr_profile2,*lowest_cost,,2014-01-14T00:00:00Z,10
*out,cgrates.org,call,*any,*any,*any,lcr_profile1,*lowest_cost,,2014-01-14T00:00:00Z,10
1 #Direction Tenant Category Account Subject DestinationId RPCategory Strategy StrategyParams ActivationTime Weight
2 *out cgrates.org call 1001 *any DST_2002 DST_1002 lcr_profile1 *static suppl2;suppl1 2014-01-14T00:00:00Z 10
3 *out cgrates.org call 1001 *any *any lcr_profile1 *static suppl1;suppl2 2014-01-14T00:00:00Z 10
4 *out cgrates.org call 1002 *any DST_2002 DST_1002 lcr_profile1 *highest_cost 2014-01-14T00:00:00Z 10
5 *out cgrates.org call 1002 *any *any lcr_profile1 *qos 2014-01-14T00:00:00Z 10
6 *out cgrates.org call 1003 *any DST_2002 DST_1002 lcr_profile1 *qos_threshold 20;;2m;;;;;;; 2014-01-14T00:00:00Z 10
7 *out cgrates.org call 1003 *any *any lcr_profile1 *qos_threshold 40;;4m;;;;;;; 40;;90s;;;;;;; 2014-01-14T00:00:00Z 10
8 *out cgrates.org call *any *any DST_2002 DST_1002 lcr_profile2 *lowest_cost 2014-01-14T00:00:00Z 10
9 *out cgrates.org call *any *any *any lcr_profile1 *lowest_cost 2014-01-14T00:00:00Z 10

View File

@@ -2,8 +2,8 @@
*out,cgrates.org,call,*any,2014-01-14T00:00:00Z,RP_RETAIL1,,
*out,cgrates.org,call,1001;1006,2014-01-14T00:00:00Z,RP_RETAIL2,,
*out,cgrates.org,call,SPECIAL_1002,2014-01-14T00:00:00Z,RP_SPECIAL_1002,,
*out,cgrates.org,lcr_profile1,supplier1,2014-01-14T00:00:00Z,RP_RETAIL1,,STATS_SUPPL1
*out,cgrates.org,lcr_profile1,supplier2,2014-01-14T00:00:00Z,RP_RETAIL2,,STATS_SUPPL2
*out,cgrates.org,lcr_profile2,supplier1,2014-01-14T00:00:00Z,RP_RETAIL2,,STATS_SUPPL1
*out,cgrates.org,lcr_profile2,supplier2,2014-01-14T00:00:00Z,RP_RETAIL1,,STATS_SUPPL2
*out,cgrates.org,lcr_profile2,supplier3,2014-01-14T00:00:00Z,RP_SPECIAL_1002,,
*out,cgrates.org,lcr_profile1,suppl1,2014-01-14T00:00:00Z,RP_RETAIL1,,STATS_SUPPL1
*out,cgrates.org,lcr_profile1,suppl2,2014-01-14T00:00:00Z,RP_RETAIL2,,STATS_SUPPL2
*out,cgrates.org,lcr_profile2,suppl1,2014-01-14T00:00:00Z,RP_RETAIL2,,STATS_SUPPL1
*out,cgrates.org,lcr_profile2,suppl2,2014-01-14T00:00:00Z,RP_RETAIL1,,STATS_SUPPL2
*out,cgrates.org,lcr_profile2,suppl3,2014-01-14T00:00:00Z,RP_SPECIAL_1002,,
1 #Direction Tenant Category Subject ActivationTime RatingPlanId RatesFallbackSubject CdrStatQueueIds
2 *out cgrates.org call *any 2014-01-14T00:00:00Z RP_RETAIL1
3 *out cgrates.org call 1001;1006 2014-01-14T00:00:00Z RP_RETAIL2
4 *out cgrates.org call SPECIAL_1002 2014-01-14T00:00:00Z RP_SPECIAL_1002
5 *out cgrates.org lcr_profile1 supplier1 suppl1 2014-01-14T00:00:00Z RP_RETAIL1 STATS_SUPPL1
6 *out cgrates.org lcr_profile1 supplier2 suppl2 2014-01-14T00:00:00Z RP_RETAIL2 STATS_SUPPL2
7 *out cgrates.org lcr_profile2 supplier1 suppl1 2014-01-14T00:00:00Z RP_RETAIL2 STATS_SUPPL1
8 *out cgrates.org lcr_profile2 supplier2 suppl2 2014-01-14T00:00:00Z RP_RETAIL1 STATS_SUPPL2
9 *out cgrates.org lcr_profile2 supplier3 suppl3 2014-01-14T00:00:00Z RP_SPECIAL_1002

View File

@@ -66,6 +66,7 @@
"rater": {
"enabled": true, // enable Rater service: <true|false>
// "balancer": "", // register to Balancer as worker: <""|internal|x.y.z.y:1234>
"cdrstats": "internal", // address where to reach the cdrstats service, empty to disable stats functionality<""|internal|x.y.z.y:1234>
},

View File

@@ -1,10 +1,11 @@
# Kamailio-CGRateS related route blocks
# Called on new connection over evapi, should normally be the case of CGRateS engine
event_route[evapi:connection-new] {
$sht(cgrconn=>cgr) = $evapi(srcaddr) + ":" + $evapi(srcport); # Detect presence of at least one connection
}
# Called when the connection with CGRateS closes
event_route[evapi:connection-closed] {
$var(connClosed) = $evapi(srcaddr) + ":" + $evapi(srcport);
if $sht(cgrconn=>cgr) == $var(connClosed) {
@@ -12,44 +13,29 @@ event_route[evapi:connection-closed] {
}
}
# Message received from CGRateS, dispatch it to own route
event_route[evapi:message-received] {
json_get_field("$evapi(msg)", "Event", "$var(Event)");
route($(var(Event){s.rm,"})); # String characters are kept by json_get_field, remove them here
}
# Called by Kamailio on new dialog
event_route[dialog:start] {
route(CGR_CALL_START);
}
# Called by Kamailio on dialog end
event_route[dialog:end] {
route(CGR_CALL_END);
}
# Send AUTH_REQUEST to CGRateS
route[CGR_AUTH_REQUEST] {
route[CGRATES_AUTH_REQUEST] {
# Auth INVITEs with CGRateS
if $sht(cgrconn=>cgr) == $null {
sl_send_reply("503","Charging controller unreachable");
exit;
}
switch ($fU) {
case 1001:
case 1006:
case 1007:
$dlg_var(cgrReqType) = "*prepaid";
break;
case 1002:
$dlg_var(cgrReqType) = "*postpaid";
break;
case 1003:
$dlg_var(cgrReqType) = "*pseudoprepaid";
break;
default:
$dlg_var(cgrReqType) = "*rated";
}
$dlg_var(cgrTenant) = "cgrates.org";
$dlg_var(cgrAccount) = $fU;
$dlg_var(cgrDestination) = $rU;
evapi_async_relay("{\"event\":\"CGR_AUTH_REQUEST\",
\"tr_index\":\"$T(id_index)\",
\"tr_label\":\"$T(id_label)\",
@@ -57,7 +43,8 @@ route[CGR_AUTH_REQUEST] {
\"cgr_tenant\":\"$dlg_var(cgrTenant)\",
\"cgr_account\":\"$dlg_var(cgrAccount)\",
\"cgr_destination\":\"$dlg_var(cgrDestination)\",
\"cgr_setuptime\":\"$TS\"}");
\"cgr_setuptime\":\"$TS\",
\"cgr_computelcr\":\"true\"}");
}
# Process AUTH_REPLY from CGRateS
@@ -65,25 +52,14 @@ route[CGR_AUTH_REPLY] {
json_get_field("$evapi(msg)", "TransactionIndex", "$var(TransactionIndex)");
json_get_field("$evapi(msg)", "TransactionLabel", "$var(TransactionLabel)");
json_get_field("$evapi(msg)", "MaxSessionTime", "$var(MaxSessionTime)");
json_get_field("$evapi(msg)", "AuthError", "$var(AuthError)");
json_get_field("$evapi(msg)", "Suppliers", "$var(Suppliers)");
json_get_field("$evapi(msg)", "Error", "$var(Error)");
$var(id_index) = $(var(TransactionIndex){s.int});
$var(id_label) = $(var(TransactionLabel){s.int});
$var(max_session_time) = $(var(MaxSessionTime){s.int});
t_continue("$var(id_index)", "$var(id_label)", "CGR_DIALOG_TIMEOUT");
}
# Check AUTH_REPLY for errors and set dialog timeout if necessary, call route(RELAY) after processing
route[CGR_DIALOG_TIMEOUT] {
if $var(AuthError) != "null" { # null is converted in string by json_get_field
xlog("CGR_AUTH_ERROR: $var(AuthError)");
sl_send_reply("503","CGR_AUTH_ERROR");
exit;
}
if $var(max_session_time) != -1 && !dlg_set_timeout("$var(max_session_time)") {
sl_send_reply("503","CGR_MAX_SESSION_TIME_ERROR");
exit;
}
route(RELAY);
$var(CgrMaxSessionTime) = $(var(MaxSessionTime){s.int});
$var(CgrSuppliers) = $(var(Suppliers){s.rm,"});
$var(CgrError) = $(var(Error){s.rm,"});
t_continue("$var(id_index)", "$var(id_label)", "CGRATES_AUTH_REPLY"); # Unpark the transaction
}
# CGRateS request for session disconnect
@@ -110,7 +86,8 @@ route[CGR_CALL_START] {
\"cgr_tenant\":\"$dlg_var(cgrTenant)\",
\"cgr_account\":\"$dlg_var(cgrAccount)\",
\"cgr_destination\":\"$dlg_var(cgrDestination)\",
\"cgr_answertime\":\"$TS\"}");
\"cgr_answertime\":\"$TS\",
\"cgr_supplier\":\"$dlg_var(cgrSupplier)\"}");
}
# Inform CGRateS about CALL_END (stop debit loops, perform accounting if desired in this way)
@@ -128,5 +105,7 @@ route[CGR_CALL_END] {
\"cgr_account\":\"$dlg_var(cgrAccount)\",
\"cgr_destination\":\"$dlg_var(cgrDestination)\",
\"cgr_answertime\":\"$dlg(start_ts)\",
\"cgr_duration\":\"$var(callDur)\"}");
}
\"cgr_duration\":\"$var(callDur)\",
\"cgr_supplier\":\"$dlg_var(cgrSupplier)\"}");
}

View File

@@ -41,7 +41,6 @@ loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "mi_rpc.so"
loadmodule "nathelper.so"
loadmodule "rtpproxy.so"
loadmodule "htable.so"
loadmodule "auth.so"
loadmodule "evapi.so"
@@ -75,9 +74,6 @@ modparam("dialog", "dlg_flag", FLT_DIALOG)
modparam("dialog", "send_bye", 1)
modparam("dialog", "timeout_noreset", 1)
# ----- rtpproxy params -----
modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:7722")
# ----- nathelper params -----
modparam("nathelper", "natping_interval", 30)
modparam("nathelper", "ping_nated_only", 1)
@@ -167,10 +163,48 @@ request_route {
route(RELAY);
}
dlg_manage();
route(CGR_AUTH_REQUEST);
switch ($fU) {
case 1001:
case 1006:
case 1007:
$dlg_var(cgrReqType) = "*prepaid";
break;
case 1002:
$dlg_var(cgrReqType) = "*postpaid";
break;
case 1003:
$dlg_var(cgrReqType) = "*pseudoprepaid";
break;
default:
$dlg_var(cgrReqType) = "*rated";
}
$dlg_var(cgrTenant) = "cgrates.org";
$dlg_var(cgrAccount) = $fU;
$dlg_var(cgrDestination) = $rU;
route(CGRATES_AUTH_REQUEST); # Will be answered in CGRATES_AUTH_REPLY
exit;
}
# Here will land requests after processing them with CGRateS. Call RELAY or other routes following this route
route[CGRATES_AUTH_REPLY] {
xlog("CGRATES_AUTH_REPLY reply, got CgrError: $var(CgrError)");
if $var(CgrError) != "" {
xlog("CGR_AUTH_ERROR: $var(CgrError)");
sl_send_reply("503","CGR_ERROR");
exit;
}
xlog("CGRATES_AUTH_REPLY, CgrMaxSessionTime: $var(CgrMaxSessionTime)");
if $var(CgrMaxSessionTime) != -1 && !dlg_set_timeout("$var(CgrMaxSessionTime)") {
sl_send_reply("503","CGR_MAX_SESSION_TIME_ERROR");
exit;
}
xlog("CGRATES_AUTH_REPLY, CgrSuppliers: $var(CgrSuppliers)");
if $var(CgrSuppliers) != "" { # Enforce the supplier variable to the first one received from CGRateS, more for testing purposes
$dlg_var(cgrSupplier) = $(var(CgrSuppliers){s.select,0,,});
}
route(RELAY);
}
# Wrapper for relaying requests
route[RELAY] {
# enable additional event routes for forwarded requests
@@ -318,8 +352,6 @@ route[NATMANAGE] {
if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB)))
return;
rtpproxy_manage("co");
if (is_request()) {
if (!has_totag()) {
if(t_is_branch_route()) {

View File

@@ -96,11 +96,13 @@ func (ub *Account) getCreditForPrefix(cd *CallDescriptor) (duration time.Duratio
// Returns the remaining credit in user's balance.
func (ub *Account) debitBalanceAction(a *Action, reset bool) error {
if a == nil {
return errors.New("nil minute action!")
return errors.New("nil minute action")
}
if a.Balance.Uuid == "" {
a.Balance.Uuid = utils.GenUUID()
}
bClone := a.Balance.Clone()
if ub.BalanceMap == nil {
ub.BalanceMap = make(map[string]BalanceChain, 1)
}
@@ -115,15 +117,17 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error {
if reset {
b.Value = 0
}
b.SubstractAmount(a.Balance.Value)
b.SubstractAmount(bClone.Value)
found = true
}
}
// if it is not found then we add it to the list
if !found {
a.Balance.Value = -a.Balance.Value
a.Balance.dirty = true // Mark the balance as dirty since we have modified and it should be checked by action triggers
ub.BalanceMap[id] = append(ub.BalanceMap[id], a.Balance)
if bClone.Value != 0 {
bClone.Value = -bClone.Value
}
bClone.dirty = true // Mark the balance as dirty since we have modified and it should be checked by action triggers
ub.BalanceMap[id] = append(ub.BalanceMap[id], bClone)
}
if a.Balance.SharedGroup != "" {
// add shared group member

View File

@@ -940,8 +940,8 @@ func TestAccountdebitBalance(t *testing.T) {
newMb := &Balance{Weight: 20, DestinationIds: "NEW"}
a := &Action{BalanceType: utils.VOICE, Direction: OUTBOUND, Balance: newMb}
ub.debitBalanceAction(a, false)
if len(ub.BalanceMap[utils.VOICE+OUTBOUND]) != 3 || ub.BalanceMap[utils.VOICE+OUTBOUND][2] != newMb {
t.Error("Error adding minute bucket!", len(ub.BalanceMap[utils.VOICE+OUTBOUND]), ub.BalanceMap[utils.VOICE+OUTBOUND])
if len(ub.BalanceMap[utils.VOICE+OUTBOUND]) != 3 || ub.BalanceMap[utils.VOICE+OUTBOUND][2].Uuid != newMb.Uuid {
t.Errorf("Error adding minute bucket! %d %+v %+v", len(ub.BalanceMap[utils.VOICE+OUTBOUND]), ub.BalanceMap[utils.VOICE+OUTBOUND][2], newMb)
}
}

View File

@@ -70,6 +70,19 @@ const (
CDRLOG = "*cdrlog"
)
func (a *Action) Clone() *Action {
return &Action{
Id: a.Id,
ActionType: a.ActionType,
BalanceType: a.BalanceType,
Direction: a.Direction,
ExtraParameters: a.ExtraParameters,
ExpirationString: a.ExpirationString,
Weight: a.Weight,
Balance: a.Balance.Clone(),
}
}
type actionTypeFunc func(*Account, *StatsQueueTriggered, *Action, Actions) error
func getActionFunc(typ string) (actionTypeFunc, bool) {
@@ -175,7 +188,6 @@ func parseTemplateValue(rsrFlds utils.RSRFields, acnt *Account, action *Action)
}
func cdrLogAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
defaultTemplate := map[string]utils.RSRFields{
"TOR": utils.ParseRSRFieldsMustCompile("balance_type", utils.INFIELD_SEP),
"CdrHost": utils.ParseRSRFieldsMustCompile("^127.0.0.1", utils.INFIELD_SEP),
@@ -255,7 +267,7 @@ func cdrLogAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions)
func resetTriggersAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
ub.ResetActionTriggers(a)
return
@@ -263,7 +275,7 @@ func resetTriggersAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Ac
func setRecurrentAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
ub.SetRecurrent(a, true)
return
@@ -271,7 +283,7 @@ func setRecurrentAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Act
func unsetRecurrentAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
ub.SetRecurrent(a, false)
return
@@ -279,7 +291,7 @@ func unsetRecurrentAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs A
func allowNegativeAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
ub.AllowNegative = true
return
@@ -287,7 +299,7 @@ func allowNegativeAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Ac
func denyNegativeAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
ub.AllowNegative = false
return
@@ -295,33 +307,35 @@ func denyNegativeAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Act
func resetAccountAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
return genericReset(ub)
}
func topupResetAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
if ub.BalanceMap == nil { // Init the map since otherwise will get error if nil
ub.BalanceMap = make(map[string]BalanceChain, 0)
}
genericMakeNegative(a)
return genericDebit(ub, a, true)
c := a.Clone()
genericMakeNegative(c)
return genericDebit(ub, c, true)
}
func topupAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
genericMakeNegative(a)
return genericDebit(ub, a, false)
c := a.Clone()
genericMakeNegative(c)
return genericDebit(ub, c, false)
}
func debitResetAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
if ub.BalanceMap == nil { // Init the map since otherwise will get error if nil
ub.BalanceMap = make(map[string]BalanceChain, 0)
@@ -331,14 +345,15 @@ func debitResetAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actio
func debitAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
return genericDebit(ub, a, false)
err = genericDebit(ub, a, false)
return
}
func resetCounterAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
uc := ub.getUnitCounter(a)
if uc == nil {
@@ -351,7 +366,7 @@ func resetCounterAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Act
func resetCountersAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
ub.UnitCounters = make([]*UnitsCounter, 0)
ub.initCounters()
@@ -366,7 +381,7 @@ func genericMakeNegative(a *Action) {
func genericDebit(ub *Account, a *Action, reset bool) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
if ub.BalanceMap == nil {
ub.BalanceMap = make(map[string]BalanceChain)
@@ -377,7 +392,7 @@ func genericDebit(ub *Account, a *Action, reset bool) (err error) {
func enableUserAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
ub.Disabled = false
return
@@ -385,7 +400,7 @@ func enableUserAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actio
func disableUserAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("Nil user balance")
return errors.New("nil user balance")
}
ub.Disabled = true
return

View File

@@ -118,7 +118,7 @@ func TestActionsLocalSetCdrlogActions(t *testing.T) {
rcvedCdrs[0].Usage != "1" ||
rcvedCdrs[0].MediationRunId != utils.META_DEFAULT ||
rcvedCdrs[0].Cost != attrsAA.Actions[0].Units {
t.Error("Received: ", rcvedCdrs[0])
t.Errorf("Received: %+v", rcvedCdrs[0])
}
}

View File

@@ -180,6 +180,7 @@ func (b *Balance) Clone() *Balance {
SharedGroup: b.SharedGroup,
TimingIDs: b.TimingIDs,
Timings: b.Timings, // should not be a problem with aliasing
dirty: b.dirty,
}
}

View File

@@ -727,6 +727,13 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) {
var cc *CallCost
var err error
if cd.account, err = accountingStorage.GetAccount(lcrCD.GetAccountKey()); err == nil {
if cd.account.Disabled {
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
Supplier: supplier,
Error: fmt.Sprintf("supplier %s is disabled", supplier),
})
continue
}
cc, err = lcrCD.debit(cd.account, true, true)
} else {
cc, err = lcrCD.GetCost()
@@ -737,7 +744,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) {
if err != nil || cc == nil {
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
Supplier: supplier,
Error: err,
Error: err.Error(),
})
} else {
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
@@ -778,11 +785,20 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) {
tccNeverConsidered := true
if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS || lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_THRESHOLD {
if stats == nil {
Logger.Warning(fmt.Sprintf("LCR_WARNING: Ignoring supplier: %s, lcr strategy: %s - no cdr_stats service configured.", supplier, lcrCost.Entry.Strategy))
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
Supplier: supplier,
Error: fmt.Sprintf("Cdr stats service not configured"),
})
continue
}
rpfKey := utils.ConcatenatedKey(ratingProfileSearchKey, supplier)
if rpf, err := dataStorage.GetRatingProfile(rpfKey, false); err == nil || rpf != nil {
if rpf, err := dataStorage.GetRatingProfile(rpfKey, false); err != nil {
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
Supplier: supplier,
Error: fmt.Sprintf("Rating plan error: %s", err.Error()),
})
continue
} else if rpf != nil {
rpf.RatingPlanActivations.Sort()
activeRas := rpf.RatingPlanActivations.GetActiveForCall(cd)
var cdrStatsQueueIds []string
@@ -793,11 +809,16 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) {
}
}
}
statsErr := false
for _, qId := range cdrStatsQueueIds {
statValues := make(map[string]float64)
if err := stats.GetValues(qId, &statValues); err != nil {
Logger.Warning(fmt.Sprintf("Error getting stats values for queue id %s: %v", qId, err))
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
Supplier: supplier,
Error: fmt.Sprintf("Get stats values for queue id %s, error %s", qId, err.Error()),
})
statsErr = true
break
}
if asr, exists := statValues[ASR]; exists {
if asr > STATS_NA {
@@ -830,6 +851,9 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) {
tccNeverConsidered = false
}
}
if statsErr { // Stats error in loop, to go next supplier
continue
}
asrValues.Sort()
acdValues.Sort()
tcdValues.Sort()
@@ -878,11 +902,19 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) {
}
}
}
var cc *CallCost
var err error
//log.Print("CD: ", lcrCD.GetAccountKey())
if cd.account, err = accountingStorage.GetAccount(lcrCD.GetAccountKey()); err == nil {
//log.Print("ACCCOUNT")
if cd.account.Disabled {
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
Supplier: supplier,
Error: fmt.Sprintf("supplier %s is disabled", supplier),
})
continue
}
cc, err = lcrCD.debit(cd.account, true, true)
} else {
//log.Print("STANDARD")
@@ -891,11 +923,10 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) {
//log.Printf("CC: %+v", cc)
supplier = utils.ConcatenatedKey(lcrCD.Direction, lcrCD.Tenant, lcrCD.Category, lcrCD.Subject)
if err != nil || cc == nil {
//lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
// Supplier: supplier,
// Error: err,
//})
Logger.Warning(fmt.Sprintf("LCR_WARNING: Ignoring supplier: %s, cannot calculate cost, error: %v", supplier, err))
lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{
Supplier: supplier,
Error: err.Error(),
})
continue
} else {
supplCost := &LCRSupplierCost{

View File

@@ -19,12 +19,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package engine
import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/cgrates/cgrates/cache2go"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
@@ -36,6 +39,75 @@ const (
LCR_STRATEGY_QOS = "*qos"
)
// A request for LCR, used in APIer and SM where we need to expose it
type LcrRequest struct {
Direction string
Tenant string
Category string
Account string
Subject string
Destination string
StartTime string
Duration string
}
func (self *LcrRequest) AsCallDescriptor() (*CallDescriptor, error) {
if len(self.Account) == 0 || len(self.Destination) == 0 {
return nil, errors.New(utils.ERR_MANDATORY_IE_MISSING)
}
// Set defaults
if len(self.Direction) == 0 {
self.Direction = utils.OUT
}
if len(self.Tenant) == 0 {
self.Tenant = config.CgrConfig().DefaultTenant
}
if len(self.Category) == 0 {
self.Category = config.CgrConfig().DefaultCategory
}
if len(self.Subject) == 0 {
self.Subject = self.Account
}
var timeStart time.Time
var err error
if len(self.StartTime) == 0 {
timeStart = time.Now()
} else if timeStart, err = utils.ParseTimeDetectLayout(self.StartTime); err != nil {
return nil, err
}
var callDur time.Duration
if len(self.Duration) == 0 {
callDur = time.Duration(1) * time.Minute
} else if callDur, err = utils.ParseDurationWithSecs(self.Duration); err != nil {
return nil, err
}
return &CallDescriptor{
Direction: self.Direction,
Tenant: self.Tenant,
Category: self.Category,
Account: self.Account,
Subject: self.Subject,
Destination: self.Destination,
TimeStart: timeStart,
TimeEnd: timeStart.Add(callDur),
}, nil
}
// A LCR reply, used in APIer and SM where we need to expose it
type LcrReply struct {
DestinationId string
RPCategory string
Strategy string
Suppliers []*LcrSupplier
}
// One supplier out of LCR reply
type LcrSupplier struct {
Supplier string
Cost float64
QOS map[string]float64
}
type LCR struct {
Direction string
Tenant string
@@ -66,7 +138,7 @@ type LCRSupplierCost struct {
Supplier string
Cost float64
Duration time.Duration
Error error
Error string // Not error due to JSON automatic serialization into struct
QOS map[string]float64
qosSortParams []string
}
@@ -211,6 +283,42 @@ func (lc *LCRCost) Sort() {
}
}
func (lc *LCRCost) HasErrors() bool {
for _, supplCost := range lc.SupplierCosts {
if len(supplCost.Error) != 0 {
return true
}
}
return false
}
func (lc *LCRCost) LogErrors() {
for _, supplCost := range lc.SupplierCosts {
if len(supplCost.Error) != 0 {
Logger.Err(fmt.Sprintf("LCR_ERROR: supplier <%s>, error <%s>", supplCost.Supplier, supplCost.Error))
}
}
}
// Returns a list of suppliers separated via
func (lc *LCRCost) SuppliersString() (string, error) {
supplStr := ""
if lc.Entry == nil {
return "", errors.New(utils.ERR_NOT_FOUND)
}
for idx, supplCost := range lc.SupplierCosts {
if dtcs, err := utils.NewDTCSFromRPKey(supplCost.Supplier); err != nil {
return "", err
} else {
if idx != 0 {
supplStr += utils.FIELDS_SEP
}
supplStr += dtcs.Subject
}
}
return supplStr, nil
}
type LowestSupplierCostSorter []*LCRSupplierCost
func (lscs LowestSupplierCostSorter) Len() int {

View File

@@ -19,9 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package engine
import (
"reflect"
"sort"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
func TestLcrQOSSorter(t *testing.T) {
@@ -201,3 +205,49 @@ func TestLcrGet(t *testing.T) {
t.Errorf("Bad lcr: %+v, %v", lcr, err)
}
}
func TestLcrRequestAsCallDescriptor(t *testing.T) {
sTime := time.Date(2015, 04, 06, 17, 40, 0, 0, time.UTC)
callDur := time.Duration(1) * time.Minute
lcrReq := &LcrRequest{Account: "1001", StartTime: sTime.String()}
if _, err := lcrReq.AsCallDescriptor(); err == nil || err.Error() != utils.ERR_MANDATORY_IE_MISSING {
t.Error("Unexpected error received: %v", err)
}
lcrReq = &LcrRequest{Account: "1001", Destination: "1002", StartTime: sTime.String()}
eCd := &CallDescriptor{
Direction: utils.OUT,
Tenant: config.CgrConfig().DefaultTenant,
Category: config.CgrConfig().DefaultCategory,
Account: lcrReq.Account,
Subject: lcrReq.Account,
Destination: lcrReq.Destination,
TimeStart: sTime,
TimeEnd: sTime.Add(callDur),
}
if cd, err := lcrReq.AsCallDescriptor(); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCd, cd) {
t.Errorf("Expected: %+v, received: %+v", eCd, cd)
}
}
func TestLCRCostSuppliersString(t *testing.T) {
lcrCost := new(LCRCost)
if _, err := lcrCost.SuppliersString(); err == nil || err.Error() != utils.ERR_NOT_FOUND {
t.Errorf("Unexpected error received: %v", err)
}
lcrCost = &LCRCost{
Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_STATIC, StrategyParams: "ivo12;dan12;rif12", Weight: 10.0},
SupplierCosts: []*LCRSupplierCost{
&LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 1.8, Duration: 60 * time.Second},
&LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second},
&LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 1.2, Duration: 60 * time.Second},
},
}
eSupplStr := "ivo12,dan12,rif12"
if supplStr, err := lcrCost.SuppliersString(); err != nil {
t.Error(err)
} else if supplStr != eSupplStr {
t.Errorf("Expecting: %s, received: %s", eSupplStr, supplStr)
}
}

View File

@@ -174,19 +174,6 @@ func TestTutFsCallsAccountsBefore(t *testing.T) {
}
}
func TestTutFsCallsCdrStats(t *testing.T) {
if !*testCalls {
return
}
var queueIds []string
eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"}
if err := tutFsCallsRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil {
t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error())
} else if len(eQueueIds) != len(queueIds) {
t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds)
}
}
// Start Pjsua as listener and register it to receive calls
func TestTutFsCallsStartPjsuaListener(t *testing.T) {
if !*testCalls {

View File

@@ -23,6 +23,7 @@ import (
"net/rpc/jsonrpc"
"os"
"path"
"reflect"
"strings"
"testing"
"time"
@@ -130,7 +131,7 @@ func TestTutKamCallsLoadTariffPlanFromFolder(t *testing.T) {
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups
}
// Make sure account was debited properly
// Make sure account was topped-up properly
func TestTutKamCallsAccountsBefore(t *testing.T) {
if !*testCalls {
return
@@ -172,16 +173,54 @@ func TestTutKamCallsAccountsBefore(t *testing.T) {
}
}
func TestTutKamCallsCdrStats(t *testing.T) {
// Make sure all stats queues are in place
func TestTutKamCallsCdrStatsBefore(t *testing.T) {
if !*testCalls {
return
}
var queueIds []string
eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil {
t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error())
} else if len(eQueueIds) != len(queueIds) {
t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds)
//eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"}
var statMetrics map[string]float64
eMetrics := map[string]float64{engine.ACC: -1, engine.ACD: -1, engine.ASR: -1}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: utils.META_DEFAULT}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST1"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACC: -1, engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1001"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1002"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1003"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL1"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACD: -1, engine.ASR: -1, engine.TCC: -1, engine.TCD: -1, engine.ACC: -1}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL2"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
}
@@ -302,6 +341,9 @@ func TestTutKamCallsCdrs(t *testing.T) {
if reply[0].Usage != "67" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Supplier != "suppl2" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
}
req = utils.RpcCdrsFilter{Accounts: []string{"1001"}, RunIds: []string{"derived_run1"}, FilterOnDerived: true}
if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil {
@@ -315,6 +357,9 @@ func TestTutKamCallsCdrs(t *testing.T) {
if reply[0].Subject != "1002" {
t.Errorf("Unexpected Subject for CDR: %+v", reply[0])
}
if reply[0].Supplier != "suppl2" {
t.Errorf("Unexpected Subject for CDR: %+v", reply[0])
}
}
req = utils.RpcCdrsFilter{Accounts: []string{"1002"}, RunIds: []string{utils.META_DEFAULT}}
if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil {
@@ -334,6 +379,9 @@ func TestTutKamCallsCdrs(t *testing.T) {
if reply[0].Usage != "61" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Supplier != "suppl1" {
t.Errorf("Unexpected Subject for CDR: %+v", reply[0])
}
}
req = utils.RpcCdrsFilter{Accounts: []string{"1003"}, RunIds: []string{utils.META_DEFAULT}}
if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil {
@@ -353,6 +401,9 @@ func TestTutKamCallsCdrs(t *testing.T) {
if reply[0].Usage != "63" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Supplier != "suppl1" {
t.Errorf("Unexpected Subject for CDR: %+v", reply[0])
}
}
req = utils.RpcCdrsFilter{Accounts: []string{"1004"}, RunIds: []string{utils.META_DEFAULT}}
if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil {
@@ -372,6 +423,9 @@ func TestTutKamCallsCdrs(t *testing.T) {
if reply[0].Usage != "62" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Supplier != "suppl1" {
t.Errorf("Unexpected Subject for CDR: %+v", reply[0])
}
}
req = utils.RpcCdrsFilter{Accounts: []string{"1006"}, RunIds: []string{utils.META_DEFAULT}}
if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil {
@@ -391,6 +445,9 @@ func TestTutKamCallsCdrs(t *testing.T) {
if reply[0].Usage != "64" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Supplier != "suppl3" {
t.Errorf("Unexpected Subject for CDR: %+v", reply[0])
}
}
req = utils.RpcCdrsFilter{Accounts: []string{"1007"}, RunIds: []string{utils.META_DEFAULT}}
if err := tutKamCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil {
@@ -410,6 +467,59 @@ func TestTutKamCallsCdrs(t *testing.T) {
if reply[0].Usage != "66" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Supplier != "suppl3" {
t.Errorf("Unexpected Subject for CDR: %+v", reply[0])
}
}
}
// Make sure all stats queues were updated
func TestTutKamCallsCdrStatsAfter(t *testing.T) {
if !*testCalls {
return
}
var statMetrics map[string]float64
eMetrics := map[string]float64{engine.ACC: 0.9707714286, engine.ACD: 64.2857142857, engine.ASR: 100}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: utils.META_DEFAULT}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACC: 0.927, engine.ACD: 63.8333333333, engine.ASR: 100, engine.TCC: 5.562, engine.TCD: 383}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST1"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.TCC: 5.562, engine.TCD: 383, engine.ACC: 0.0217, engine.ACD: 67, engine.ASR: 100}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1001"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACD: 61, engine.ASR: 100, engine.TCC: 5.562, engine.TCD: 383, engine.ACC: 1.2334}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1002"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.TCC: 5.562, engine.TCD: 383, engine.ACC: 1.2334, engine.ACD: -1, engine.ASR: -1}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "CDRST_1003"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACC: 1.2334, engine.ACD: 62, engine.ASR: 100, engine.TCC: 3.7002, engine.TCD: 186}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL1"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
eMetrics = map[string]float64{engine.ACD: 67, engine.ASR: 100, engine.TCC: 1.2551, engine.TCD: 134, engine.ACC: 0.62755}
if err := tutKamCallsRpc.Call("CDRStatsV1.GetMetrics", v1.AttrGetMetrics{StatsQueueId: "STATS_SUPPL2"}, &statMetrics); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(eMetrics, statMetrics) {
t.Errorf("Expecting: %v, received: %v", eMetrics, statMetrics)
}
}

View File

@@ -42,7 +42,7 @@ func TestTutLocalInitCfg(t *testing.T) {
if !*testLocal {
return
}
tutLocalCfgPath = path.Join(*dataDir, "conf", "samples", "cgradmin")
tutLocalCfgPath = path.Join(*dataDir, "conf", "samples", "tutlocal")
// Init config first
var err error
tutFsLocalCfg, err = config.NewCGRConfigFromFolder(tutLocalCfgPath)
@@ -116,7 +116,8 @@ func TestTutLocalCacheStats(t *testing.T) {
return
}
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1}
expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 3, RatingProfiles: 8, Actions: 6, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1,
DerivedChargers: 1, LcrProfiles: 4}
var args utils.AttrCacheStats
if err := tutLocalRpc.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil {
t.Error("Got error on ApierV1.GetCacheStats: ", err.Error())
@@ -156,6 +157,17 @@ func TestTutLocalGetCachedItemAge(t *testing.T) {
} else if rcvAge.SharedGroup > time.Duration(2)*time.Second {
t.Errorf("Cache too old: %d", rcvAge)
}
if err := tutLocalRpc.Call("ApierV1.GetCachedItemAge", "*out:cgrates.org:call:1001:*any", &rcvAge); err != nil {
t.Error("Got error on ApierV1.GetCachedItemAge: ", err.Error())
} else if rcvAge.SharedGroup > time.Duration(2)*time.Second {
t.Errorf("Cache too old: %d", rcvAge)
}
if err := tutLocalRpc.Call("ApierV1.GetCachedItemAge", "*out:cgrates.org:call:*any:*any", &rcvAge); err != nil {
t.Error("Got error on ApierV1.GetCachedItemAge: ", err.Error())
} else if rcvAge.SharedGroup > time.Duration(2)*time.Second {
t.Errorf("Cache too old: %d", rcvAge)
}
/*
if err := tutLocalRpc.Call("ApierV1.GetCachedItemAge", "1006", &rcvAge); err != nil {
t.Error("Got error on ApierV1.GetCachedItemAge: ", err.Error())
@@ -312,6 +324,398 @@ func TestTutLocalMaxDebit(t *testing.T) {
}
}
// Make sure queueids were created
func TestTutFsCallsCdrStats(t *testing.T) {
if !*testCalls {
return
}
var queueIds []string
eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"}
if err := tutLocalRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil {
t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error())
} else if len(eQueueIds) != len(queueIds) {
t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds)
}
}
// Check LCR
func TestTutLocalLcrStatic(t *testing.T) {
if !*testLocal {
return
}
tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z")
tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z")
cd := engine.CallDescriptor{
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: tStart,
TimeEnd: tEnd,
}
eStLcr := &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_STATIC, StrategyParams: "suppl2;suppl1", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 0.6, Duration: 60 * time.Second},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second},
},
}
var lcr engine.LCRCost
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
cd = engine.CallDescriptor{
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1001",
Account: "1001",
Destination: "1003",
TimeStart: tStart,
TimeEnd: tEnd,
}
eStLcr = &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_STATIC, StrategyParams: "suppl1;suppl2", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second},
},
}
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
}
func TestTutLocalLcrHighestCost(t *testing.T) {
if !*testLocal {
return
}
tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z")
tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z")
cd := engine.CallDescriptor{
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1002",
Account: "1002",
Destination: "1002",
TimeStart: tStart,
TimeEnd: tEnd,
}
eStLcr := &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_HIGHEST, StrategyParams: "", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 0.6, Duration: 60 * time.Second},
},
}
var lcr engine.LCRCost
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
}
func TestTutLocalLcrQos(t *testing.T) {
if !*testLocal {
return
}
tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z")
tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z")
cd := engine.CallDescriptor{
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1002",
Account: "1002",
Destination: "1003",
TimeStart: tStart,
TimeEnd: tEnd,
}
eStLcr := &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS, StrategyParams: "", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: -1, engine.ACC: -1, engine.TCC: -1, engine.ASR: -1, engine.ACD: -1}},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: -1, engine.ACC: -1, engine.TCC: -1, engine.ASR: -1, engine.ACD: -1}},
},
}
eStLcr2 := &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS, StrategyParams: "", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: -1, engine.ACC: -1, engine.TCC: -1, engine.ASR: -1, engine.ACD: -1}},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: -1, engine.ACC: -1, engine.TCC: -1, engine.ASR: -1, engine.ACD: -1}},
},
}
var lcr engine.LCRCost
// Since there is no real quality difference, the suppliers will come in random order here
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eStLcr2.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
// Post some CDRs to influence stats
testCdr1 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()),
TOR: utils.VOICE, AccId: "testcdr1", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED,
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC),
Usage: time.Duration(2) * time.Minute, Supplier: "suppl1",
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}}
testCdr2 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr2", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()),
TOR: utils.VOICE, AccId: "testcdr2", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED,
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1002", Subject: "1002", Destination: "1003",
SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC),
Usage: time.Duration(90) * time.Second, Supplier: "suppl2",
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}}
var reply string
for _, cdr := range []*engine.StoredCdr{testCdr1, testCdr2} {
if err := tutLocalRpc.Call("CdrsV2.ProcessCdr", cdr, &reply); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply received: ", reply)
}
}
// Based on stats, supplier1 should always be better since he has a higer ACD
eStLcr = &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS, StrategyParams: "", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 90, engine.ACC: 0.325, engine.TCC: 0.325, engine.ASR: 100, engine.ACD: 90}},
},
}
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eStLcr2.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[1], lcr.SupplierCosts[1])
}
testCdr3 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr3", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()),
TOR: utils.VOICE, AccId: "testcdr3", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED,
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1004",
SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC),
Usage: time.Duration(180) * time.Second, Supplier: "suppl2"}
if err := tutLocalRpc.Call("CdrsV2.ProcessCdr", testCdr3, &reply); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply received: ", reply)
}
// Since ACD has considerably increased for supplier2, we should have it as first prio now
eStLcr = &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS, StrategyParams: "", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 270, engine.ACC: 0.3625, engine.TCC: 0.725, engine.ASR: 100, engine.ACD: 135}},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}},
},
}
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eStLcr2.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
}
func TestTutLocalLcrQosThreshold(t *testing.T) {
if !*testLocal {
return
}
tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z")
tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z")
cd := engine.CallDescriptor{
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1003",
Account: "1003",
Destination: "1002",
TimeStart: tStart,
TimeEnd: tEnd,
}
eLcr := &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "20;;2m;;;;;;;", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 0.6, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 270, engine.ACC: 0.3625, engine.TCC: 0.725, engine.ASR: 100, engine.ACD: 135}},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}},
},
}
var lcr engine.LCRCost
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eLcr.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
testCdr4 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr4", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()),
TOR: utils.VOICE, AccId: "testcdr4", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED,
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1004",
SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC),
Usage: time.Duration(60) * time.Second, Supplier: "suppl2"}
var reply string
if err := tutLocalRpc.Call("CdrsV2.ProcessCdr", testCdr4, &reply); err != nil { // Should drop ACD under the 2m required by threshold, removing suppl2 from lcr
t.Error("Unexpected error: ", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply received: ", reply)
}
eLcr = &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "20;;2m;;;;;;;", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}},
},
}
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eLcr.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
cd = engine.CallDescriptor{
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1003",
Account: "1003",
Destination: "1004",
TimeStart: tStart,
TimeEnd: tEnd,
}
eLcr = &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "40;;90s;;;;;;;", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 330, engine.ACC: 0.3416666667, engine.TCC: 1.025, engine.ASR: 100, engine.ACD: 110}},
},
}
eLcr2 := &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "40;;90s;;;;;;;", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 330, engine.ACC: 0.3416666667, engine.TCC: 1.025, engine.ASR: 100, engine.ACD: 110}},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}},
},
}
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eLcr.SupplierCosts, lcr.SupplierCosts) && !reflect.DeepEqual(eLcr2.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eLcr.SupplierCosts[1], lcr.SupplierCosts[1])
}
testCdr5 := &engine.StoredCdr{CgrId: utils.Sha1("testcdr5", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()),
TOR: utils.VOICE, AccId: "testcdr5", CdrHost: "192.168.1.1", CdrSource: "TEST_QOS_LCR", ReqType: utils.META_RATED,
Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1004",
SetupTime: time.Date(2014, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2014, 12, 7, 8, 42, 26, 0, time.UTC),
Usage: time.Duration(1) * time.Second, Supplier: "suppl2"}
if err := tutLocalRpc.Call("CdrsV2.ProcessCdr", testCdr5, &reply); err != nil { // Should drop ACD under the 1m required by threshold, removing suppl2 from lcr
t.Error("Unexpected error: ", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply received: ", reply)
}
eLcr = &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "40;;90s;;;;;;;", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second,
QOS: map[string]float64{engine.TCD: 240, engine.ACC: 0.35, engine.TCC: 0.7, engine.ASR: 100, engine.ACD: 120}},
},
}
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eLcr.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
}
func TestTutLocalLeastCost(t *testing.T) {
if !*testLocal {
return
}
tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z")
tEnd, _ := utils.ParseDate("2014-08-04T13:01:00Z")
cd := engine.CallDescriptor{
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1004",
Account: "1004",
Destination: "1002",
TimeStart: tStart,
TimeEnd: tEnd,
}
eStLcr := &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: "DST_1002", RPCategory: "lcr_profile2", Strategy: engine.LCR_STRATEGY_LOWEST, StrategyParams: "", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile2:suppl3", Cost: 0.01, Duration: 60 * time.Second},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile2:suppl1", Cost: 0.6, Duration: 60 * time.Second},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile2:suppl2", Cost: 1.2, Duration: 60 * time.Second},
},
}
var lcr engine.LCRCost
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[2], lcr.SupplierCosts[2])
}
cd = engine.CallDescriptor{
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1004",
Account: "1004",
Destination: "1003",
TimeStart: tStart,
TimeEnd: tEnd,
}
eStLcr = &engine.LCRCost{
Entry: &engine.LCREntry{DestinationId: utils.ANY, RPCategory: "lcr_profile1", Strategy: engine.LCR_STRATEGY_LOWEST, StrategyParams: "", Weight: 10.0},
SupplierCosts: []*engine.LCRSupplierCost{
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl1", Cost: 1.2, Duration: 60 * time.Second},
&engine.LCRSupplierCost{Supplier: "*out:cgrates.org:lcr_profile1:suppl2", Cost: 1.2, Duration: 60 * time.Second},
},
}
if err := tutLocalRpc.Call("Responder.GetLCR", cd, &lcr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStLcr.Entry, lcr.Entry) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.Entry, lcr.Entry)
} else if !reflect.DeepEqual(eStLcr.SupplierCosts, lcr.SupplierCosts) {
t.Errorf("Expecting: %+v, received: %+v", eStLcr.SupplierCosts[0], lcr.SupplierCosts[0])
}
}
func TestTutLocalStopCgrEngine(t *testing.T) {
if !*testLocal {
return

View File

@@ -172,19 +172,6 @@ func TestTutOsipsCallsAccountsBefore(t *testing.T) {
}
}
func TestTutOsipsCallsCdrStats(t *testing.T) {
if !*testCalls {
return
}
var queueIds []string
eQueueIds := []string{"*default", "CDRST1", "CDRST_1001", "CDRST_1002", "CDRST_1003", "STATS_SUPPL1", "STATS_SUPPL2"}
if err := tutOsipsCallsRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil {
t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error())
} else if len(eQueueIds) != len(queueIds) {
t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds)
}
}
// Start Pjsua as listener and register it to receive calls
func TestTutOsipsCallsStartPjsuaListener(t *testing.T) {
if !*testCalls {

View File

@@ -1,3 +1,3 @@
#!/usr/bin/env sh
docker run --rm -p 3306:3306 -p 6379:6379 -p 2012:2012 -itv /home/rif/Documents/prog/go/src/github.com/cgrates/cgrates:/root/code/src/github.com/cgrates/cgrates --name cgr cgrates
docker run --rm -p 3306:3306 -p 6379:6379 -p 2012:2012 -p 2013:2013 -p 2080:2080 -itv `pwd`:/root/code/src/github.com/cgrates/cgrates --name cgr cgrates

View File

@@ -48,12 +48,13 @@ func (self *KamailioSessionManager) onCgrAuth(evData []byte, connId string) {
kev, err := NewKamEvent(evData)
if err != nil {
engine.Logger.Info(fmt.Sprintf("<SM-Kamailio> ERROR unmarshalling event: %s, error: %s", evData, err.Error()))
return
}
if kev.GetReqType(utils.META_DEFAULT) == utils.META_NONE { // Do not process this request
return
}
if kev.MissingParameter() {
if kar, err := kev.AsKamAuthReply(0.0, errors.New(utils.ERR_MANDATORY_IE_MISSING)); err != nil {
if kar, err := kev.AsKamAuthReply(0.0, "", errors.New(utils.ERR_MANDATORY_IE_MISSING)); err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed building auth reply %s", err.Error()))
} else if err = self.conns[connId].Send(kar.String()); err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed sending auth reply %s", err.Error()))
@@ -61,29 +62,75 @@ func (self *KamailioSessionManager) onCgrAuth(evData []byte, connId string) {
return
}
var remainingDuration float64
if err = self.rater.GetDerivedMaxSessionTime(*kev.AsStoredCdr(), &remainingDuration); err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Could not get max session time for %s, error: %s", kev.GetUUID(), err.Error()))
var errMaxSession error
if errMaxSession = self.rater.GetDerivedMaxSessionTime(*kev.AsStoredCdr(), &remainingDuration); errMaxSession != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Could not get max session time, error: %s", errMaxSession.Error()))
}
if kar, err := kev.AsKamAuthReply(remainingDuration, err); err != nil {
var supplStr string
var errSuppl error
if kev.ComputeLcr() {
if supplStr, errSuppl = self.getSuppliers(kev); errSuppl != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Could not get suppliers, error: %s", errSuppl.Error()))
}
}
if errMaxSession == nil { // Overwrite the error from maxSessionTime with the one from suppliers if nil
errMaxSession = errSuppl
}
if kar, err := kev.AsKamAuthReply(remainingDuration, supplStr, errMaxSession); err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed building auth reply %s", err.Error()))
} else if err = self.conns[connId].Send(kar.String()); err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed sending auth reply %s", err.Error()))
}
}
func (self *KamailioSessionManager) onCgrLcrReq(evData []byte, connId string) {
kev, err := NewKamEvent(evData)
if err != nil {
engine.Logger.Info(fmt.Sprintf("<SM-Kamailio> ERROR unmarshalling event: %s, error: %s", string(evData), err.Error()))
return
}
supplStr, err := self.getSuppliers(kev)
kamLcrReply, errReply := kev.AsKamAuthReply(-1.0, supplStr, err)
kamLcrReply.Event = CGR_LCR_REPLY // Hit the CGR_LCR_REPLY event route on Kamailio side
if errReply != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed building auth reply %s", errReply.Error()))
} else if err = self.conns[connId].Send(kamLcrReply.String()); err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed sending lcr reply %s", err.Error()))
}
}
func (self *KamailioSessionManager) getSuppliers(kev KamEvent) (string, error) {
cd, err := kev.AsCallDescriptor()
if err != nil {
engine.Logger.Info(fmt.Sprintf("<SM-Kamailio> LCR_PREPROCESS_ERROR error: %s", err.Error()))
return "", errors.New("LCR_PREPROCESS_ERROR")
}
var lcr engine.LCRCost
if err = self.Rater().GetLCR(cd, &lcr); err != nil {
engine.Logger.Info(fmt.Sprintf("<SM-Kamailio> LCR_API_ERROR error: %s", err.Error()))
return "", errors.New("LCR_API_ERROR")
}
if lcr.HasErrors() {
lcr.LogErrors()
return "", errors.New("LCR_COMPUTE_ERROR")
}
return lcr.SuppliersString()
}
func (self *KamailioSessionManager) onCallStart(evData []byte, connId string) {
kamEv, err := NewKamEvent(evData)
if err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> ERROR unmarshalling event: %s, error: %s", evData, err.Error()))
return
}
if kamEv.GetReqType(utils.META_DEFAULT) == utils.META_NONE { // Do not process this request
return
}
if kamEv.MissingParameter() {
self.DisconnectSession(kamEv, "", utils.ERR_MANDATORY_IE_MISSING)
self.DisconnectSession(kamEv, connId, utils.ERR_MANDATORY_IE_MISSING)
return
}
s := NewSession(kamEv, "", self)
s := NewSession(kamEv, connId, self)
if s != nil {
self.sessions = append(self.sessions, s)
}
@@ -93,6 +140,7 @@ func (self *KamailioSessionManager) onCallEnd(evData []byte, connId string) {
kev, err := NewKamEvent(evData)
if err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-Kamailio> ERROR unmarshalling event: %s, error: %s", evData, err.Error()))
return
}
if kev.GetReqType(utils.META_DEFAULT) == utils.META_NONE { // Do not process this request
return
@@ -115,6 +163,7 @@ func (self *KamailioSessionManager) Connect() error {
var err error
eventHandlers := map[*regexp.Regexp][]func([]byte, string){
regexp.MustCompile("CGR_AUTH_REQUEST"): []func([]byte, string){self.onCgrAuth},
regexp.MustCompile("CGR_LCR_REQUEST"): []func([]byte, string){self.onCgrLcrReq},
regexp.MustCompile("CGR_CALL_START"): []func([]byte, string){self.onCallStart},
regexp.MustCompile("CGR_CALL_END"): []func([]byte, string){self.onCallEnd},
}
@@ -135,6 +184,7 @@ func (self *KamailioSessionManager) Connect() error {
}
func (self *KamailioSessionManager) DisconnectSession(ev engine.Event, connId, notify string) error {
engine.Logger.Debug(fmt.Sprintf("DisconnectSession, ev: %+v, connId: %s, notify: %s", ev, connId, notify))
sessionIds := ev.GetSessionIds()
disconnectEv := &KamSessionDisconnect{Event: CGR_SESSION_DISCONNECT, HashEntry: sessionIds[0], HashId: sessionIds[1], Reason: notify}
if err := self.conns[connId].Send(disconnectEv.String()); err != nil {

View File

@@ -33,7 +33,9 @@ import (
const (
EVENT = "event"
CGR_AUTH_REQUEST = "CGR_AUTH_REQUEST"
CGR_LCR_REQUEST = "CGR_LCR_REQUEST"
CGR_AUTH_REPLY = "CGR_AUTH_REPLY"
CGR_LCR_REPLY = "CGR_LCR_REPLY"
CGR_SESSION_DISCONNECT = "CGR_SESSION_DISCONNECT"
CGR_CALL_START = "CGR_CALL_START"
CGR_CALL_END = "CGR_CALL_END"
@@ -41,10 +43,11 @@ const (
CGR_ANSWERTIME = "cgr_answertime"
CGR_STOPTIME = "cgr_stoptime"
CGR_DURATION = "cgr_duration"
KAM_TR_INDEX = "tr_index"
KAM_TR_LABEL = "tr_label"
HASH_ENTRY = "h_entry"
HASH_ID = "h_id"
KAM_TR_INDEX = "tr_index"
KAM_TR_LABEL = "tr_label"
HASH_ENTRY = "h_entry"
HASH_ID = "h_id"
)
var primaryFields = []string{EVENT, CALLID, FROM_TAG, HASH_ENTRY, HASH_ID, CGR_ACCOUNT, CGR_SUBJECT, CGR_DESTINATION,
@@ -55,7 +58,8 @@ type KamAuthReply struct {
TransactionIndex int // Original transaction index
TransactionLabel int // Original transaction label
MaxSessionTime int // Maximum session time in case of success, -1 for unlimited
AuthError error // Reply in case of error
Suppliers string // List of suppliers, comma separated
Error string // Reply in case of error
}
func (self *KamAuthReply) String() string {
@@ -63,6 +67,18 @@ func (self *KamAuthReply) String() string {
return string(mrsh)
}
type KamLcrReply struct {
Event string
Suppliers string
Error error
}
func (self *KamLcrReply) String() string {
self.Event = CGR_LCR_REPLY
mrsh, _ := json.Marshal(self)
return string(mrsh)
}
type KamSessionDisconnect struct {
Event string
HashEntry string
@@ -201,6 +217,12 @@ func (kev KamEvent) GetExtraFields() map[string]string {
func (kev KamEvent) GetCdrSource() string {
return "KAMAILIO_" + kev.GetName()
}
func (kev KamEvent) ComputeLcr() bool {
compute, _ := strconv.ParseBool(kev[utils.CGR_COMPUTELCR])
return compute
}
func (kev KamEvent) MissingParameter() bool {
var nullTime time.Time
switch kev.GetName() {
@@ -211,6 +233,10 @@ func (kev KamEvent) MissingParameter() bool {
return len(kev.GetAccount(utils.META_DEFAULT)) == 0 ||
len(kev.GetDestination(utils.META_DEFAULT)) == 0 ||
len(kev[KAM_TR_INDEX]) == 0 || len(kev[KAM_TR_LABEL]) == 0
case CGR_LCR_REQUEST:
return len(kev.GetAccount(utils.META_DEFAULT)) == 0 ||
len(kev.GetDestination(utils.META_DEFAULT)) == 0 ||
len(kev[KAM_TR_INDEX]) == 0 || len(kev[KAM_TR_LABEL]) == 0
case CGR_CALL_START:
if aTime, err := kev.GetAnswerTime(utils.META_DEFAULT); err != nil || aTime == nullTime {
return true
@@ -310,9 +336,12 @@ func (kev KamEvent) String() string {
return string(mrsh)
}
func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, authErr error) (*KamAuthReply, error) {
func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, suppliers string, resErr error) (*KamAuthReply, error) {
var err error
kar := &KamAuthReply{Event: CGR_AUTH_REPLY}
kar := &KamAuthReply{Event: CGR_AUTH_REPLY, Suppliers: suppliers}
if resErr != nil {
kar.Error = resErr.Error()
}
if _, hasIt := kev[KAM_TR_INDEX]; !hasIt {
return nil, fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, KAM_TR_INDEX)
}
@@ -330,7 +359,20 @@ func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, authErr error) (*KamA
maxSessionTime = maxSessionDur.Seconds()
}
kar.MaxSessionTime = int(utils.Round(maxSessionTime, 0, utils.ROUNDING_MIDDLE))
kar.AuthError = authErr
return kar, nil
}
// Converts into CallDescriptor due to responder interface needs
func (kev KamEvent) AsCallDescriptor() (*engine.CallDescriptor, error) {
lcrReq := &engine.LcrRequest{
Direction: kev.GetDirection(utils.META_DEFAULT),
Tenant: kev.GetTenant(utils.META_DEFAULT),
Category: kev.GetCategory(utils.META_DEFAULT),
Account: kev.GetAccount(utils.META_DEFAULT),
Subject: kev.GetSubject(utils.META_DEFAULT),
Destination: kev.GetDestination(utils.META_DEFAULT),
StartTime: utils.FirstNonEmpty(kev[CGR_SETUPTIME], kev[CGR_ANSWERTIME]),
Duration: kev[CGR_DURATION],
}
return lcrReq.AsCallDescriptor()
}

View File

@@ -21,7 +21,9 @@ package sessionmanager
import (
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -57,8 +59,8 @@ func TestNewKamEvent(t *testing.T) {
}
func TestKevAsKamAuthReply(t *testing.T) {
expectedKar := &KamAuthReply{Event: CGR_AUTH_REPLY, TransactionIndex: 29223, TransactionLabel: 698469260, MaxSessionTime: 1200}
if rcvKar, err := kamEv.AsKamAuthReply(1200000000000.0, nil); err != nil {
expectedKar := &KamAuthReply{Event: CGR_AUTH_REPLY, TransactionIndex: 29223, TransactionLabel: 698469260, MaxSessionTime: 1200, Suppliers: "supplier1,supplier2"}
if rcvKar, err := kamEv.AsKamAuthReply(1200000000000.0, "supplier1,supplier2", nil); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedKar, rcvKar) {
t.Error("Received KAR: ", rcvKar)
@@ -79,6 +81,15 @@ func TestKevMissingParameter(t *testing.T) {
if !kamEv.MissingParameter() {
t.Error("Failed detecting missing parameters")
}
kamEv = KamEvent{"event": CGR_LCR_REQUEST, "tr_index": "36045", "tr_label": "612369399", "cgr_reqtype": utils.META_POSTPAID,
"cgr_account": "1001"}
if !kamEv.MissingParameter() {
t.Error("Failed detecting missing parameters")
}
kamEv = KamEvent{"event": CGR_LCR_REQUEST, CGR_ACCOUNT: "1001", CGR_DESTINATION: "1002", "tr_index": "36045", "tr_label": "612369399"}
if kamEv.MissingParameter() {
t.Error("False detecting missing parameters")
}
kamEv = KamEvent{"event": "CGR_CALL_START", "callid": "9d28ec3ee068babdfe036623f42c0969@0:0:0:0:0:0:0:0", "from_tag": "3131b566",
"cgr_reqtype": utils.META_POSTPAID, "cgr_account": "1001", "cgr_destination": "1002"}
if !kamEv.MissingParameter() {
@@ -91,3 +102,24 @@ func TestKevMissingParameter(t *testing.T) {
t.Error("False detecting missing parameters")
}
}
func TestKevAsCallDescriptor(t *testing.T) {
sTime := time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC)
kamEv := KamEvent{"event": CGR_LCR_REQUEST, CGR_ACCOUNT: "1001", CGR_DESTINATION: "1002", CGR_SETUPTIME: sTime.String()}
eCd := &engine.CallDescriptor{
Direction: utils.OUT,
Tenant: config.CgrConfig().DefaultTenant,
Category: config.CgrConfig().DefaultCategory,
Account: kamEv[CGR_ACCOUNT],
Subject: kamEv[CGR_ACCOUNT],
Destination: kamEv[CGR_DESTINATION],
TimeStart: sTime,
TimeEnd: sTime.Add(time.Duration(1) * time.Minute),
}
if cd, err := kamEv.AsCallDescriptor(); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCd, cd) {
t.Errorf("Expecting: %+v, received: %+v", eCd, cd)
}
}

View File

@@ -634,6 +634,7 @@ type ApiReloadCache struct {
AccAliases []string
LCRIds []string
DerivedChargers []string
LcrProfiles []string
}
type AttrCacheStats struct { // Add in the future filters here maybe so we avoid counting complete cache
@@ -648,6 +649,7 @@ type CacheStats struct {
RatingAliases int
AccountAliases int
DerivedChargers int
LcrProfiles int
}
type AttrCachedItemAge struct {
@@ -664,6 +666,7 @@ type CachedItemAge struct {
RatingAlias time.Duration
AccountAlias time.Duration
DerivedChargers time.Duration
LcrProfiles time.Duration
}
type AttrExpFileCdrs struct {

View File

@@ -186,6 +186,7 @@ const (
CGR_SUPPLIER = "cgr_supplier"
DISCONNECT_CAUSE = "disconnect_cause"
CGR_DISCONNECT_CAUSE = "cgr_disconnect_cause"
CGR_COMPUTELCR = "cgr_computelcr"
)
var (