mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-17 22:29:55 +05:00
Merge branch 'master' into load
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
135
apier/v1/lcr.go
135
apier/v1/lcr.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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,,,,,,
|
||||
|
||||
|
@@ -2,4 +2,3 @@
|
||||
DST_1002,1002
|
||||
DST_1003,1003
|
||||
DST_FS,10
|
||||
DST_2002,2002
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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,,
|
||||
|
||||
|
@@ -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>
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -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)\"}");
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
110
engine/lcr.go
110
engine/lcr.go
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -186,6 +186,7 @@ const (
|
||||
CGR_SUPPLIER = "cgr_supplier"
|
||||
DISCONNECT_CAUSE = "disconnect_cause"
|
||||
CGR_DISCONNECT_CAUSE = "cgr_disconnect_cause"
|
||||
CGR_COMPUTELCR = "cgr_computelcr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user