ResourceLimit with AllocationMessage

This commit is contained in:
DanB
2017-05-11 21:20:56 +02:00
parent 3f959ae267
commit 4d41c6552c
15 changed files with 168 additions and 82 deletions

View File

@@ -402,6 +402,7 @@ CREATE TABLE tp_resource_limits (
`activation_interval` varchar(64) NOT NULL,
`usage_ttl` varchar(32) NOT NULL,
`limit` varchar(64) NOT NULL,
`allocation_message` varchar(64) NOT NULL,
`weight` decimal(8,2) NOT NULL,
`action_trigger_ids` varchar(64) NOT NULL,
`created_at` TIMESTAMP,

View File

@@ -396,8 +396,9 @@ CREATE TABLE tp_resource_limits (
"filter_field_values" varchar(256) NOT NULL,
"activation_interval" varchar(64) NOT NULL,
"usage_ttl" varchar(32) NOT NULL,
"weight" decimal(8,2) NOT NULL,
"limit" varchar(64) NOT NULL,
"allocation_message" varchar(64) NOT NULL,
"weight" decimal(8,2) NOT NULL,
"action_trigger_ids" varchar(64) NOT NULL,
"created_at" TIMESTAMP WITH TIME ZONE
);

View File

@@ -1,6 +1,6 @@
#Id,FilterType,FilterFieldName,FilterFieldValues,ActivationInterval,TTL,Limit,Weight,ActionTriggers
ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,2,10,
ResGroup1,*string_prefix,Destination,10;20,,,,,
ResGroup1,*rsr_fields,,Subject(~^1.*1$);Destination(1002),,,,,
ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,2,10,
ResGroup2,*cdr_stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,
#Id,FilterType,FilterFieldName,FilterFieldValues,ActivationInterval,TTL,Limit,AllocationReply,Weight,ActionTriggers
ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,2,,10,
ResGroup1,*string_prefix,Destination,10;20,,,,,,
ResGroup1,*rsr_fields,,Subject(~^1.*1$);Destination(1002),,,,,,
ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,2,,SPECIAL_1002,10,
ResGroup2,*cdr_stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,,
1 #Id #Id,FilterType,FilterFieldName,FilterFieldValues,ActivationInterval,TTL,Limit,AllocationReply,Weight,ActionTriggers FilterType FilterFieldName FilterFieldValues ActivationInterval TTL Limit Weight ActionTriggers
2 ResGroup1 ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,1s,2,,10, *string Account 1001;1002 2014-07-29T15:00:00Z 1s 2 10
3 ResGroup1 ResGroup1,*string_prefix,Destination,10;20,,,,,, *string_prefix Destination 10;20
4 ResGroup1 ResGroup1,*rsr_fields,,Subject(~^1.*1$);Destination(1002),,,,,, *rsr_fields Subject(~^1.*1$);Destination(1002)
5 ResGroup2 ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,3600s,2,,SPECIAL_1002,10, *destinations Destination DST_FS 2014-07-29T15:00:00Z 3600s 2 10
6 ResGroup2 ResGroup2,*cdr_stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,,,, *cdr_stats CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20

View File

@@ -117,13 +117,13 @@ route[CGRATES_RL_REQUEST] {
\"cgr_setuptime\":\"$TS\"}");
}
# Process LCR_REPLY from CGRateS
# Process RL_REPLY from CGRateS
route[CGR_RL_REPLY] {
json_get_field("$evapi(msg)", "ResourceAllowed", "$var(CGRResourceAllowed)");
json_get_field("$evapi(msg)", "ResourceAllocated", "$var(CGRResourceAllocated)");
json_get_field("$evapi(msg)", "Error", "$var(CgrError)");
$var(id_index) = $(var(TransactionIndex){s.int});
$var(id_label) = $(var(TransactionLabel){s.int});
$var(CGRResourceAllowed) = $(var(CGRResourceAllowed){s.rm,"});
$var(CGRResourceAllocated) = $(var(CGRResourceAllocated){s.rm,"});
$var(CgrError) = $(var(CgrError){s.rm,"});
t_continue("$var(id_index)", "$var(id_label)", "CGRATES_RL_REPLY"); # Unpark the transaction
}

View File

@@ -220,7 +220,7 @@ route[CGRATES_RL_REPLY] {
sl_send_reply("503","CGR_ERROR");
exit;
}
if $var(CGRResourceAllowed) == "false" {
if $var(CGRResourceAllocated) == "false" {
sl_send_reply("403","Resource not allowed");
exit;
}

View File

@@ -267,11 +267,11 @@ cgrates.org,mas,true,another,value,10
`
resLimits = `
#Id,FilterType,FilterFieldName,FilterFieldValues,ActivationInterval,TTL,Limit,Weight,ActionTriggers
ResGroup21,*string,HdrAccount,1001;1002,2014-07-29T15:00:00Z,1s,2,10,
ResGroup21,*string_prefix,HdrDestination,10;20,,,,,
ResGroup21,*rsr_fields,,HdrSubject(~^1.*1$);HdrDestination(1002),,,,,
ResGroup22,*destinations,HdrDestination,DST_FS,2014-07-29T15:00:00Z,3600s,2,10,
#Id,FilterType,FilterFieldName,FilterFieldValues,ActivationInterval,TTL,Limit,AllocationMessage,Weight,ActionTriggers
ResGroup21,*string,HdrAccount,1001;1002,2014-07-29T15:00:00Z,1s,2,call,10,
ResGroup21,*string_prefix,HdrDestination,10;20,,,,,,
ResGroup21,*rsr_fields,,HdrSubject(~^1.*1$);HdrDestination(1002),,,,,,
ResGroup22,*destinations,HdrDestination,DST_FS,2014-07-29T15:00:00Z,3600s,2,premium_call,10,
`
)
@@ -329,7 +329,6 @@ func init() {
log.Print("error in LoadAliases:", err)
}
if err := csvr.LoadResourceLimits(); err != nil {
log.Print("error in LoadResourceLimits:", err)
}
csvr.WriteToDatabase(false, false, false)
cache.Flush()
@@ -1387,9 +1386,10 @@ func TestLoadResourceLimits(t *testing.T) {
ActivationInterval: &utils.TPActivationInterval{
ActivationTime: "2014-07-29T15:00:00Z",
},
UsageTTL: "1s",
Weight: 10,
Limit: "2",
UsageTTL: "1s",
AllocationMessage: "call",
Weight: 10,
Limit: "2",
},
"ResGroup22": &utils.TPResourceLimit{
TPid: testTPID,
@@ -1400,15 +1400,15 @@ func TestLoadResourceLimits(t *testing.T) {
ActivationInterval: &utils.TPActivationInterval{
ActivationTime: "2014-07-29T15:00:00Z",
},
UsageTTL: "3600s",
Weight: 10,
Limit: "2",
UsageTTL: "3600s",
AllocationMessage: "premium_call",
Weight: 10,
Limit: "2",
},
}
if len(csvr.resLimits) != len(eResLimits) {
t.Error("Failed to load resourcelimits: ", len(csvr.resLimits))
}
if !reflect.DeepEqual(eResLimits["ResGroup22"], csvr.resLimits["ResGroup22"]) {
} else if !reflect.DeepEqual(eResLimits["ResGroup22"], csvr.resLimits["ResGroup22"]) {
t.Errorf("Expecting: %+v, received: %+v", eResLimits["ResGroup22"], csvr.resLimits["ResGroup22"])
}

View File

@@ -1845,6 +1845,9 @@ func (tps TpResourceLimits) AsTPResourceLimits() (result []*utils.TPResourceLimi
if tp.Limit != "" {
rl.Limit = tp.Limit
}
if tp.AllocationMessage != "" {
rl.AllocationMessage = tp.AllocationMessage
}
if len(tp.ActivationInterval) != 0 {
rl.ActivationInterval = new(utils.TPActivationInterval)
aiSplt := strings.Split(tp.ActivationInterval, utils.INFIELD_SEP)
@@ -1888,6 +1891,7 @@ func APItoModelResourceLimit(rl *utils.TPResourceLimit) (mdls TpResourceLimits)
mdl.UsageTTL = rl.UsageTTL
mdl.Weight = rl.Weight
mdl.Limit = rl.Limit
mdl.AllocationMessage = rl.AllocationMessage
if rl.ActivationInterval != nil {
if rl.ActivationInterval.ActivationTime != "" {
mdl.ActivationInterval = rl.ActivationInterval.ActivationTime
@@ -1921,6 +1925,11 @@ func APItoModelResourceLimit(rl *utils.TPResourceLimit) (mdls TpResourceLimits)
func APItoResourceLimit(tpRL *utils.TPResourceLimit, timezone string) (rl *ResourceLimit, err error) {
rl = &ResourceLimit{ID: tpRL.ID, Weight: tpRL.Weight,
Filters: make([]*RequestFilter, len(tpRL.Filters)), Usage: make(map[string]*ResourceUsage)}
if tpRL.UsageTTL != "" {
if rl.UsageTTL, err = utils.ParseDurationWithSecs(tpRL.UsageTTL); err != nil {
return nil, err
}
}
for i, f := range tpRL.Filters {
rf := &RequestFilter{Type: f.Type, FieldName: f.FieldName, Values: f.Values}
if err := rf.CompileValues(); err != nil {

View File

@@ -460,8 +460,9 @@ type TpResourceLimit struct {
ActivationInterval string `index:"4" re:""`
UsageTTL string `index:"5" re:""`
Limit string `index:"6" re:""`
Weight float64 `index:"7" re:"\d+\.?\d*"`
ActionTriggerIds string `index:"8" re:""`
AllocationMessage string `index:"7" re:""`
Weight float64 `index:"8" re:"\d+\.?\d*"`
ActionTriggerIds string `index:"9" re:""`
CreatedAt time.Time
}

View File

@@ -30,9 +30,9 @@ import (
)
type ResourceUsage struct {
ID string // Unique identifier of this resourceUsage, Eg: FreeSWITCH UUID
UsageTime time.Time // So we can expire it later
UsageUnits float64 // Number of units used
ID string // Unique identifier of this ResourceUsage, Eg: FreeSWITCH UUID
Time time.Time // So we can expire it later
Units float64 // Number of units used
}
// ResourceLimit represents a limit imposed for accessing a resource (eg: new calls)
@@ -46,23 +46,22 @@ type ResourceLimit struct {
Limit float64 // Limit value
ActionTriggers ActionTriggers // Thresholds to check after changing Limit
UsageTTL time.Duration // Expire usage after this duration
AllocationMessage string // message returned by the winning resourceLimit on allocation
Usage map[string]*ResourceUsage // Keep a record of usage, bounded with timestamps so we can expire too long records
TotalUsage float64 // internal counter aggregating real usage of ResourceLimit
}
func (rl *ResourceLimit) removeExpiredUnits() {
for ruID, rv := range rl.Usage {
if time.Now().Sub(rv.UsageTime) <= rl.UsageTTL {
if time.Now().Sub(rv.Time) <= rl.UsageTTL {
continue // not expired
}
delete(rl.Usage, ruID)
rl.TotalUsage -= rv.UsageUnits
rl.TotalUsage -= rv.Units
}
}
func (rl *ResourceLimit) UsedUnits() float64 {
rl.Lock()
defer rl.Unlock()
if rl.UsageTTL != 0 {
rl.removeExpiredUnits()
}
@@ -70,25 +69,21 @@ func (rl *ResourceLimit) UsedUnits() float64 {
}
func (rl *ResourceLimit) RecordUsage(ru *ResourceUsage) (err error) {
rl.Lock()
defer rl.Unlock()
if _, hasID := rl.Usage[ru.ID]; hasID {
return fmt.Errorf("Duplicate resource usage with id: %s", ru.ID)
}
rl.Usage[ru.ID] = ru
rl.TotalUsage += ru.UsageUnits
rl.TotalUsage += ru.Units
return
}
func (rl *ResourceLimit) ClearUsage(ruID string) error {
rl.Lock()
defer rl.Unlock()
ru, hasIt := rl.Usage[ruID]
if !hasIt {
return fmt.Errorf("Cannot find usage record with id: %s", ruID)
}
delete(rl.Usage, ru.ID)
rl.TotalUsage -= ru.UsageUnits
rl.TotalUsage -= ru.Units
return nil
}
@@ -100,19 +95,7 @@ func (rls ResourceLimits) Sort() {
sort.Slice(rls, func(i, j int) bool { return rls[i].Weight > rls[j].Weight })
}
// AllowUsage checks limits and decides whether the usage is allowed
func (rls ResourceLimits) AllowUsage(usage float64) (allowed bool) {
if len(rls) != 0 { // if rules defined, they need to allow usage
for _, rl := range rls {
if rl.Limit < rl.UsedUnits()+usage {
return false
}
}
}
return true
}
// RecordUsage will record the usage in all the resource limits
// RecordUsage will record the usage in all the resource limits, failing back on errors
func (rls ResourceLimits) RecordUsage(ru *ResourceUsage) (err error) {
var failedAtIdx int
for i, rl := range rls {
@@ -124,6 +107,7 @@ func (rls ResourceLimits) RecordUsage(ru *ResourceUsage) (err error) {
if err != nil {
for _, rl := range rls[:failedAtIdx] {
rl.ClearUsage(ru.ID) // best effort
rl.TotalUsage -= ru.Units
}
}
return
@@ -131,6 +115,10 @@ func (rls ResourceLimits) RecordUsage(ru *ResourceUsage) (err error) {
// ClearUsage gives back the units to the pool
func (rls ResourceLimits) ClearUsage(ruID string) {
for _, rl := range rls {
rl.Lock()
defer rl.Unlock()
}
for _, rl := range rls {
if err := rl.ClearUsage(ruID); err != nil {
utils.Logger.Warning(fmt.Sprintf("<ResourceLimits>, err: %s", err.Error()))
@@ -139,6 +127,44 @@ func (rls ResourceLimits) ClearUsage(ruID string) {
return
}
// AllocateResource attempts allocating resources for a *ResourceUsage
// simulates on dryRun
// returns utils.ErrResourceUnavailable if allocation is not possible
func (rls ResourceLimits) AllocateResource(ru *ResourceUsage, dryRun bool) (alcMessage string, err error) {
if len(rls) == 0 {
return utils.META_NONE, nil
}
// lock resources so we can safely take decisions, need all to be locked before proceeding
for _, rl := range rls {
if dryRun {
rl.RLock()
defer rl.RUnlock()
} else {
rl.Lock()
defer rl.Unlock()
}
}
// Simulate resource usage
for _, rl := range rls {
if rl.Limit >= rl.UsedUnits()+ru.Units {
if alcMessage == "" {
alcMessage = rl.AllocationMessage
}
if alcMessage == "" { // rl.AllocationMessage is not populated
alcMessage = rl.ID
}
}
}
if alcMessage == "" {
return "", utils.ErrResourceUnavailable
}
if dryRun {
return
}
err = rls.RecordUsage(ru)
return
}
// Pas the config as a whole so we can ask access concurrently
func NewResourceLimiterService(cfg *config.CGRConfig, dataDB DataDB, cdrStatS rpcclient.RpcClientConnection) (*ResourceLimiterService, error) {
if cdrStatS != nil && reflect.ValueOf(cdrStatS).IsNil() {
@@ -254,12 +280,19 @@ func (rls *ResourceLimiterService) V1ResourceLimitsForEvent(ev map[string]interf
return nil
}
func (rls *ResourceLimiterService) V1AllowUsage(attrs utils.AttrRLsResourceUsage, allow *bool) (err error) {
matchingRLForEv, err := rls.matchingResourceLimitsForEvent(attrs.Event)
func (rls *ResourceLimiterService) V1AllowUsage(args utils.AttrRLsResourceUsage, allow *bool) (err error) {
mtcRLs, err := rls.matchingResourceLimitsForEvent(args.Event)
if err != nil {
return utils.NewErrServerError(err)
}
*allow = matchingRLForEv.AllowUsage(attrs.Units)
if _, err = mtcRLs.AllocateResource(&ResourceUsage{ID: args.UsageID,
Time: time.Now(), Units: args.Units}, false); err != nil {
if err == utils.ErrResourceUnavailable {
return // not error but still not allowed
}
return utils.NewErrServerError(err)
}
*allow = true
return
}
@@ -269,14 +302,12 @@ func (rls *ResourceLimiterService) V1AllocateResource(args utils.AttrRLsResource
if err != nil {
return utils.NewErrServerError(err)
}
if !mtcRLs.AllowUsage(args.Units) {
return utils.ErrResourceUnavailable
if alcMsg, err := mtcRLs.AllocateResource(&ResourceUsage{ID: args.UsageID,
Time: time.Now(), Units: args.Units}, false); err != nil {
return err
} else {
*reply = alcMsg
}
if err = mtcRLs.RecordUsage(&ResourceUsage{ID: args.UsageID,
UsageTime: time.Now(), UsageUnits: args.Units}); err != nil {
return
}
*reply = utils.OK
return
}

View File

@@ -172,6 +172,45 @@ func TestRLsIndexStringFilters(t *testing.T) {
}
}
*/
/*
func TestResourceLimitsAllowUsage(t *testing.T) {
rls := make(ResourceLimits, 0)
if !rls.AllowUsage(1.0) {
t.Error("Not allowed for empty limits")
}
rls = ResourceLimits{
&ResourceLimit{
ID: "RLAU1",
Weight: 20,
Filters: []*RequestFilter{
&RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"1001", "1002"}},
&RequestFilter{Type: MetaRSRFields, Values: []string{"Subject(~^1.*1$)", "Destination(1002)"},
rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP),
}},
ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)},
Limit: 1,
Usage: map[string]*ResourceUsage{"call1": &ResourceUsage{
ID: "call1", UsageTime: time.Now(), UsageUnits: 1}},
TotalUsage: 1,
},
&ResourceLimit{
ID: "RLAU2",
Weight: 10,
Filters: []*RequestFilter{
&RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"dan", "1002"}},
&RequestFilter{Type: MetaString, FieldName: "Subject", Values: []string{"dan"}},
},
ActivationInterval: &utils.ActivationInterval{ActivationTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC)},
Limit: 2,
UsageTTL: time.Duration(1 * time.Millisecond),
Usage: make(map[string]*ResourceUsage),
},
}
if !rls.AllowUsage(2.0) {
t.Error("Not allowed")
}
}
*/
func TestRLsLoadRLs(t *testing.T) {
rls := []*ResourceLimit{
@@ -347,7 +386,7 @@ func TestRLsV1InitiateResourceUsage(t *testing.T) {
var reply string
if err := rLS.V1AllocateResource(attrRU, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
} else if reply != "RL1" {
t.Error("Received reply: ", reply)
}
resLimits, err := rLS.matchingResourceLimitsForEvent(attrRU.Event)

View File

@@ -380,8 +380,8 @@ func (rs *Responder) GetDerivedMaxSessionTime(ev *CDR, reply *float64) error {
maxCallDuration = remainingDuration
}
}
rs.getCache().Cache(cacheKey, &cache.CacheItem{Value: maxCallDuration})
*reply = maxCallDuration
rs.getCache().Cache(cacheKey, &cache.CacheItem{Value: reply})
return nil
}

View File

@@ -93,7 +93,7 @@ func (self *KamailioSessionManager) onCgrAuth(evData []byte, connId string) {
return
}
if kev.MissingParameter(self.timezone) {
if kar, err := kev.AsKamAuthReply(0.0, "", false, utils.ErrMandatoryIeMissing); err != nil {
if kar, err := kev.AsKamAuthReply(0.0, "", false, "", utils.ErrMandatoryIeMissing); err != nil {
utils.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed building auth reply %s", err.Error()))
} else if err = self.conns[connId].Send(kar.String()); err != nil {
utils.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed sending auth reply %s", err.Error()))
@@ -123,7 +123,7 @@ func (self *KamailioSessionManager) onCgrAuth(evData []byte, connId string) {
resourceAllowed = false
}
}
if kar, err := kev.AsKamAuthReply(remainingDuration, supplStr, resourceAllowed, errReply); err != nil {
if kar, err := kev.AsKamAuthReply(remainingDuration, supplStr, resourceAllowed, "", errReply); err != nil {
utils.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed building auth reply %s", err.Error()))
} else if err = self.conns[connId].Send(kar.String()); err != nil {
utils.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed sending auth reply %s", err.Error()))
@@ -137,7 +137,7 @@ func (self *KamailioSessionManager) onCgrLcrReq(evData []byte, connId string) {
return
}
supplStr, err := self.getSuppliers(kev)
kamLcrReply, errReply := kev.AsKamAuthReply(0, supplStr, false, err)
kamLcrReply, errReply := kev.AsKamAuthReply(0, supplStr, false, "", err)
kamLcrReply.Event = CGR_LCR_REPLY // Hit the CGR_LCR_REPLY event route on Kamailio side
if errReply != nil {
utils.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed building LCR reply %s", errReply.Error()))
@@ -158,7 +158,7 @@ func (self *KamailioSessionManager) onCgrRLReq(evData []byte, connId string) {
utils.Logger.Err(fmt.Sprintf("<SM-Kamailio> RLs error: %s", err.Error()))
resourceAllowed = false
}
kamRLReply, errReply := kev.AsKamAuthReply(0, "", resourceAllowed, err)
kamRLReply, errReply := kev.AsKamAuthReply(0, "", resourceAllowed, "", err)
kamRLReply.Event = CGR_RL_REPLY // Hit the CGR_LCR_REPLY event route on Kamailio side
if errReply != nil {
utils.Logger.Err(fmt.Sprintf("<SM-Kamailio> Failed building RL reply %s", errReply.Error()))

View File

@@ -55,13 +55,14 @@ var primaryFields = []string{EVENT, CALLID, FROM_TAG, HASH_ENTRY, HASH_ID, CGR_A
CGR_CATEGORY, CGR_TENANT, CGR_REQTYPE, CGR_ANSWERTIME, CGR_SETUPTIME, CGR_STOPTIME, CGR_DURATION, CGR_PDD, utils.CGR_SUPPLIER, utils.CGR_DISCONNECT_CAUSE}
type KamAuthReply struct {
Event string // Kamailio will use this to differentiate between requests and replies
TransactionIndex int // Original transaction index
TransactionLabel int // Original transaction label
MaxSessionTime int // Maximum session time in case of success, -1 for unlimited
Suppliers string // List of suppliers, comma separated
ResourceAllowed bool
Error string // Reply in case of error
Event string // Kamailio will use this to differentiate between requests and replies
TransactionIndex int // Original transaction index
TransactionLabel int // Original transaction label
MaxSessionTime int // Maximum session time in case of success, -1 for unlimited
Suppliers string // List of suppliers, comma separated
ResourceAllocated bool
AllocationMessage string
Error string // Reply in case of error
}
func (self *KamAuthReply) String() string {
@@ -349,8 +350,10 @@ func (kev KamEvent) String() string {
return string(mrsh)
}
func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, suppliers string, resAllowed bool, rplyErr error) (kar *KamAuthReply, err error) {
kar = &KamAuthReply{Event: CGR_AUTH_REPLY, Suppliers: suppliers}
func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, suppliers string,
resAllocated bool, allocationMessage string, rplyErr error) (kar *KamAuthReply, err error) {
kar = &KamAuthReply{Event: CGR_AUTH_REPLY, Suppliers: suppliers,
ResourceAllocated: resAllocated, AllocationMessage: allocationMessage}
if rplyErr != nil {
kar.Error = rplyErr.Error()
}
@@ -371,7 +374,7 @@ func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, suppliers string, res
maxSessionTime = maxSessionDur.Seconds()
}
kar.MaxSessionTime = int(utils.Round(maxSessionTime, 0, utils.ROUNDING_MIDDLE))
kar.ResourceAllowed = resAllowed
return kar, nil
}

View File

@@ -61,8 +61,8 @@ func TestNewKamEvent(t *testing.T) {
func TestKevAsKamAuthReply(t *testing.T) {
expectedKar := &KamAuthReply{Event: CGR_AUTH_REPLY, TransactionIndex: 29223, TransactionLabel: 698469260,
MaxSessionTime: 1200, ResourceAllowed: true, Suppliers: "supplier1,supplier2"}
if rcvKar, err := kamEv.AsKamAuthReply(1200000000000.0, "supplier1,supplier2", true, nil); err != nil {
MaxSessionTime: 1200, ResourceAllocated: true, Suppliers: "supplier1,supplier2"}
if rcvKar, err := kamEv.AsKamAuthReply(1200000000000.0, "supplier1,supplier2", true, "", nil); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedKar, rcvKar) {
t.Error("Received KAR: ", rcvKar)

View File

@@ -1267,7 +1267,8 @@ type TPResourceLimit struct {
Filters []*TPRequestFilter // Filters for the request
ActivationInterval *TPActivationInterval // Time when this limit becomes active/expires
UsageTTL string
Limit string // Limit value
Limit string // Limit value
AllocationMessage string
Weight float64 // Weight to sort the ResourceLimits
ActionTriggerIDs []string // Thresholds to check after changing Limit
}