diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index 9c9ba3318..4f650505d 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -399,9 +399,10 @@ CREATE TABLE tp_resource_limits ( `filter_type` varchar(16) NOT NULL, `filter_field_name` varchar(64) NOT NULL, `filter_field_values` varchar(256) NOT NULL, - `activation_time` varchar(24) NOT NULL, - `weight` decimal(8,2) NOT NULL, + `activation_interval` varchar(64) NOT NULL, + `usage_ttl` varchar(32) NOT NULL, `limit` varchar(64) NOT NULL, + `weight` decimal(8,2) NOT NULL, `action_trigger_ids` varchar(64) NOT NULL, `created_at` TIMESTAMP, PRIMARY KEY (`id`), diff --git a/data/storage/postgres/create_tariffplan_tables.sql b/data/storage/postgres/create_tariffplan_tables.sql index e6ce162a5..48951f270 100644 --- a/data/storage/postgres/create_tariffplan_tables.sql +++ b/data/storage/postgres/create_tariffplan_tables.sql @@ -394,7 +394,8 @@ CREATE TABLE tp_resource_limits ( "filter_type" varchar(16) NOT NULL, "filter_field_name" varchar(64) NOT NULL, "filter_field_values" varchar(256) NOT NULL, - "activation_time" varchar(24) 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, "action_trigger_ids" varchar(64) NOT NULL, diff --git a/data/tariffplans/tutorial/ResourceLimits.csv b/data/tariffplans/tutorial/ResourceLimits.csv index b09181721..a78c36730 100644 --- a/data/tariffplans/tutorial/ResourceLimits.csv +++ b/data/tariffplans/tutorial/ResourceLimits.csv @@ -1,6 +1,6 @@ -#Id,FilterType,FilterFieldName,FilterFieldValues,ActivationTime,Weight,Limit,ActionTriggers -ResGroup1,*string,Account,1001;1002,2014-07-29T15:00:00Z,10,2, -ResGroup1,*string_prefix,Destination,10;20,2014-07-29T15:00:00Z,10,, -ResGroup1,*rsr_fields,,Subject(~^1.*1$);Destination(1002),,,, -ResGroup2,*destinations,Destination,DST_FS,2014-07-29T15:00:00Z,10,2, -ResGroup2,*cdr_stats,,CDRST1:*min_ASR:34;CDRST_1001:*min_ASR:20,,,, +#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,,,,, diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 3ac2c581b..df8dfadef 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -267,11 +267,11 @@ cgrates.org,mas,true,another,value,10 ` resLimits = ` -#Id,FilterType,FilterFieldName,FilterFieldValues,ActivationTime,Weight,Limit,ActionTriggers -ResGroup21,*string,HdrAccount,1001;1002,2014-07-29T15:00:00Z,10,2, -ResGroup21,*string_prefix,HdrDestination,10;20,2014-07-29T15:00:00Z,10,, -ResGroup21,*rsr_fields,,HdrSubject(~^1.*1$);HdrDestination(1002),,,, -ResGroup22,*destinations,HdrDestination,DST_FS,2014-07-29T15:00:00Z,10,2, +#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, ` ) diff --git a/engine/model_helpers.go b/engine/model_helpers.go index c99b67395..43a3b255b 100644 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -1832,11 +1832,21 @@ func (tps TpResourceLimits) AsTPResourceLimits() (result []*utils.TPResourceLimi rl, found := mrl[tp.Tag] if !found { rl = &utils.TPResourceLimit{ - TPid: tp.Tpid, - ID: tp.Tag, - ActivationTime: tp.ActivationTime, - Weight: tp.Weight, - Limit: tp.Limit, + TPid: tp.Tpid, + ID: tp.Tag, + UsageTTL: tp.UsageTTL, + Weight: tp.Weight, + Limit: tp.Limit, + } + if len(tp.ActivationInterval) != 0 { + tpAI := new(utils.TPActivationInterval) + aiSplt := strings.Split(tp.ActivationInterval, utils.INFIELD_SEP) + if len(aiSplt) == 2 { + tpAI.ActivationTime = aiSplt[0] + tpAI.ExpiryTime = aiSplt[1] + } else if len(aiSplt) == 1 { + tpAI.ActivationTime = aiSplt[0] + } } } if tp.ActionTriggerIds != "" { @@ -1859,56 +1869,52 @@ func (tps TpResourceLimits) AsTPResourceLimits() (result []*utils.TPResourceLimi return } -func APItoModelResourceLimit(rl *utils.TPResourceLimit) TpResourceLimits { - result := TpResourceLimits{} - for _, f := range rl.Filters { - tp := &TpResourceLimit{ - Tpid: rl.TPid, - Tag: rl.ID, - ActivationTime: rl.ActivationTime, - Weight: rl.Weight, - Limit: rl.Limit, - } - for i, atid := range rl.ActionTriggerIDs { - if i != 0 { - tp.ActionTriggerIds = tp.ActionTriggerIds + utils.INFIELD_SEP + atid - } else { - tp.ActionTriggerIds = atid - } - } - tp.FilterType = f.Type - tp.FilterFieldName = f.FieldName - for i, val := range f.Values { - if i != 0 { - tp.FilterFieldValues = tp.FilterFieldValues + utils.INFIELD_SEP + val - } else { - tp.FilterFieldValues = val - } - } - result = append(result, tp) - } +func APItoModelResourceLimit(rl *utils.TPResourceLimit) (mdls TpResourceLimits) { if len(rl.Filters) == 0 { - tp := &TpResourceLimit{ - Tpid: rl.TPid, - Tag: rl.ID, - ActivationTime: rl.ActivationTime, - Weight: rl.Weight, - Limit: rl.Limit, + return + } + for i, fltr := range rl.Filters { + mdl := &TpResourceLimit{ + Tpid: rl.TPid, + Tag: rl.ID, } - for i, atid := range rl.ActionTriggerIDs { - if i != 0 { - tp.ActionTriggerIds = tp.ActionTriggerIds + utils.INFIELD_SEP + atid - } else { - tp.ActionTriggerIds = atid + if i == 0 { + mdl.UsageTTL = rl.UsageTTL + mdl.Weight = rl.Weight + mdl.Limit = rl.Limit + if rl.ActivationInterval != nil { + if rl.ActivationInterval.ActivationTime != "" { + mdl.ActivationInterval = rl.ActivationInterval.ActivationTime + } + if rl.ActivationInterval.ExpiryTime != "" { + mdl.ActivationInterval += utils.INFIELD_SEP + rl.ActivationInterval.ExpiryTime + } + } + for i, atid := range rl.ActionTriggerIDs { + if i != 0 { + mdl.ActionTriggerIds = mdl.ActionTriggerIds + utils.INFIELD_SEP + atid + } else { + mdl.ActionTriggerIds = atid + } } } - result = append(result, tp) + mdl.FilterType = fltr.Type + mdl.FilterFieldName = fltr.FieldName + for i, val := range fltr.Values { + if i != 0 { + mdl.FilterFieldValues = mdl.FilterFieldValues + utils.INFIELD_SEP + val + } else { + mdl.FilterFieldValues = val + } + } + mdls = append(mdls, mdl) } - return result + return } 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)} + rl = &ResourceLimit{ID: tpRL.ID, Weight: tpRL.Weight, + Filters: make([]*RequestFilter, len(tpRL.Filters)), Usage: make(map[string]*ResourceUsage)} for i, f := range tpRL.Filters { rf := &RequestFilter{Type: f.Type, FieldName: f.FieldName, Values: f.Values} if err := rf.CompileValues(); err != nil { @@ -1916,8 +1922,10 @@ func APItoResourceLimit(tpRL *utils.TPResourceLimit, timezone string) (rl *Resou } rl.Filters[i] = rf } - if rl.ActivationTime, err = utils.ParseTimeDetectLayout(tpRL.ActivationTime, timezone); err != nil { - return nil, err + if tpRL.ActivationInterval != nil { + if rl.ActivationInterval, err = tpRL.ActivationInterval.AsActivationInterval(timezone); err != nil { + return nil, err + } } if rl.Limit, err = strconv.ParseFloat(tpRL.Limit, 64); err != nil { return nil, err diff --git a/engine/models.go b/engine/models.go index 0302e2a85..5a6026154 100644 --- a/engine/models.go +++ b/engine/models.go @@ -451,17 +451,18 @@ func (t TBLSMCosts) TableName() string { } type TpResourceLimit struct { - ID int64 - Tpid string - Tag string `index:"0" re:""` - FilterType string `index:"1" re:"^\*[A-Za-z].*"` - FilterFieldName string `index:"2" re:""` - FilterFieldValues string `index:"3" re:""` - ActivationTime string `index:"4" re:""` - Weight float64 `index:"5" re:"\d+\.?\d*"` - Limit string `index:"6" re:""` - ActionTriggerIds string `index:"7" re:""` - CreatedAt time.Time + ID int64 + Tpid string + Tag string `index:"0" re:""` + FilterType string `index:"1" re:"^\*[A-Za-z].*"` + FilterFieldName string `index:"2" re:""` + FilterFieldValues string `index:"3" re:""` + 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:""` + CreatedAt time.Time } type TBLVersion struct { diff --git a/engine/reslimiter.go b/engine/reslimiter.go index baeec2d29..674aad316 100644 --- a/engine/reslimiter.go +++ b/engine/reslimiter.go @@ -37,17 +37,17 @@ type ResourceUsage struct { // ResourceLimit represents a limit imposed for accessing a resource (eg: new calls) type ResourceLimit struct { - sync.Mutex - ID string // Identifier of this limit - Filters []*RequestFilter // Filters for the request - ActivationTime time.Time // Time when this limit becomes active - ExpiryTime time.Time - Weight float64 // Weight to sort the ResourceLimits - Limit float64 // Limit value - ActionTriggers ActionTriggers // Thresholds to check after changing Limit - UsageTTL time.Duration // Expire usage after this duration - Usage map[string]*ResourceUsage // Keep a record of usage, bounded with timestamps so we can expire too long records - usageCounter float64 // internal counter aggregating real usage of ResourceLimit + sync.RWMutex + ID string // Identifier of this limit + Filters []*RequestFilter // Filters for the request + ActivationInterval *utils.ActivationInterval // Time when this limit becomes active and expires + ExpiryTime time.Time + Weight float64 // Weight to sort the ResourceLimits + Limit float64 // Limit value + ActionTriggers ActionTriggers // Thresholds to check after changing Limit + UsageTTL time.Duration // Expire usage after this duration + 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() { @@ -56,7 +56,7 @@ func (rl *ResourceLimit) removeExpiredUnits() { continue // not expired } delete(rl.Usage, ruID) - rl.usageCounter -= rv.UsageUnits + rl.TotalUsage -= rv.UsageUnits } } @@ -66,7 +66,7 @@ func (rl *ResourceLimit) UsedUnits() float64 { if rl.UsageTTL != 0 { rl.removeExpiredUnits() } - return rl.usageCounter + return rl.TotalUsage } func (rl *ResourceLimit) RecordUsage(ru *ResourceUsage) (err error) { @@ -76,7 +76,7 @@ func (rl *ResourceLimit) RecordUsage(ru *ResourceUsage) (err error) { return fmt.Errorf("Duplicate resource usage with id: %s", ru.ID) } rl.Usage[ru.ID] = ru - rl.usageCounter += ru.UsageUnits + rl.TotalUsage += ru.UsageUnits return } @@ -88,7 +88,7 @@ func (rl *ResourceLimit) ClearUsage(ruID string) error { return fmt.Errorf("Cannot find usage record with id: %s", ruID) } delete(rl.Usage, ru.ID) - rl.usageCounter -= ru.UsageUnits + rl.TotalUsage -= ru.UsageUnits return nil } @@ -189,8 +189,7 @@ func (rls *ResourceLimiterService) matchingResourceLimitsForEvent(ev map[string] } return nil, err } - now := time.Now() - if rl.ActivationTime.After(now) || (!rl.ExpiryTime.IsZero() && rl.ExpiryTime.Before(now)) { // not active + if rl.ActivationInterval != nil && !rl.ActivationInterval.IsActiveAtTime(time.Now()) { // not active continue } passAllFilters := true @@ -223,8 +222,7 @@ func (rls *ResourceLimiterService) matchingResourceLimitsForEvent(ev map[string] } return nil, err } - now := time.Now() - if rl.ActivationTime.After(now) || (!rl.ExpiryTime.IsZero() && rl.ExpiryTime.Before(now)) { // not active + if rl.ActivationInterval != nil && !rl.ActivationInterval.IsActiveAtTime(time.Now()) { // not active continue } for _, fltr := range rl.Filters { diff --git a/utils/activinterval.go b/utils/activinterval.go new file mode 100644 index 000000000..08865767b --- /dev/null +++ b/utils/activinterval.go @@ -0,0 +1,31 @@ +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ +package utils + +import ( + "time" +) + +type ActivationInterval struct { + ActivationTime, ExpiryTime time.Time +} + +func (ai *ActivationInterval) IsActiveAtTime(atTime time.Time) bool { + return ai.ActivationTime.Before(atTime) && + (ai.ExpiryTime.IsZero() || ai.ExpiryTime.After(atTime)) +} diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 8253524c7..7ebda38bb 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -1262,13 +1262,14 @@ type AttrSetBalance struct { } type TPResourceLimit struct { - TPid string - ID string // Identifier of this limit - Filters []*TPRequestFilter // Filters for the request - ActivationTime string // Time when this limit becomes active - Weight float64 // Weight to sort the ResourceLimits - Limit string // Limit value - ActionTriggerIDs []string // Thresholds to check after changing Limit + TPid string + ID string // Identifier of this limit + Filters []*TPRequestFilter // Filters for the request + ActivationInterval *TPActivationInterval // Time when this limit becomes active/expires + UsageTTL string + Limit string // Limit value + Weight float64 // Weight to sort the ResourceLimits + ActionTriggerIDs []string // Thresholds to check after changing Limit } type TPRequestFilter struct { @@ -1277,6 +1278,12 @@ type TPRequestFilter struct { Values []string // Filter definition } +// TPActivationInterval represents an activation interval for an item +type TPActivationInterval struct { + ActivationTime, + ExpiryTime string +} + type AttrRLsCache struct { LoadID string ResourceLimitIDs []string @@ -1288,6 +1295,18 @@ type AttrRLsResourceUsage struct { Units float64 } +// AsActivationTime converts TPActivationInterval into ActivationInterval +func (tpAI *TPActivationInterval) AsActivationInterval(timezone string) (ai *ActivationInterval, err error) { + var at, et time.Time + if at, err = ParseTimeDetectLayout(tpAI.ActivationTime, timezone); err != nil { + return + } + if et, err = ParseTimeDetectLayout(tpAI.ExpiryTime, timezone); err != nil { + return + } + return &ActivationInterval{ActivationTime: at, ExpiryTime: et}, nil +} + // Attributes to send on SessionDisconnect by SMG type AttrDisconnectSession struct { EventStart map[string]interface{}