Files
cgrates/utils/resources.go

428 lines
11 KiB
Go

/*
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 Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>
*/
package utils
import (
"time"
)
// ResourceProfile represents the user configuration for the resource
type ResourceProfile struct {
Tenant string
ID string // identifier of this resource
FilterIDs []string
UsageTTL time.Duration // auto-expire the usage after this duration
Limit float64 // limixt value
AllocationMessage string // message returned by the winning resource on allocation
Blocker bool // blocker flag to stop processing on filters matched
Stored bool
Weights DynamicWeights // Weight to sort the resources
ThresholdIDs []string // Thresholds to check after changing Limit
}
// Clone clones *ResourceProfile (lkID excluded)
func (rp *ResourceProfile) Clone() *ResourceProfile {
if rp == nil {
return nil
}
clone := &ResourceProfile{
Tenant: rp.Tenant,
ID: rp.ID,
UsageTTL: rp.UsageTTL,
Limit: rp.Limit,
AllocationMessage: rp.AllocationMessage,
Blocker: rp.Blocker,
Stored: rp.Stored,
}
if rp.FilterIDs != nil {
clone.FilterIDs = make([]string, len(rp.FilterIDs))
copy(clone.FilterIDs, rp.FilterIDs)
}
if rp.ThresholdIDs != nil {
clone.ThresholdIDs = make([]string, len(rp.ThresholdIDs))
copy(clone.ThresholdIDs, rp.ThresholdIDs)
}
if rp.Weights != nil {
clone.Weights = rp.Weights.Clone()
}
return clone
}
// CacheClone returns a clone of ResourceProfile used by ltcache CacheCloner
func (rp *ResourceProfile) CacheClone() any {
return rp.Clone()
}
// ResourceProfileWithAPIOpts is used in replicatorV1 for dispatcher
type ResourceProfileWithAPIOpts struct {
*ResourceProfile
APIOpts map[string]any
}
// TenantID returns unique identifier of the ResourceProfile in a multi-tenant environment
func (rp *ResourceProfile) TenantID() string {
return ConcatenatedKey(rp.Tenant, rp.ID)
}
// ResourceUsage represents an usage counted
type ResourceUsage struct {
Tenant string
ID string // Unique identifier of this ResourceUsage, Eg: FreeSWITCH UUID
ExpiryTime time.Time
Units float64 // Number of units used
}
// TenantID returns the concatenated key between tenant and ID
func (ru *ResourceUsage) TenantID() string {
return ConcatenatedKey(ru.Tenant, ru.ID)
}
// isActive checks ExpiryTime at some time
func (ru *ResourceUsage) IsActive(atTime time.Time) bool {
return ru.ExpiryTime.IsZero() || ru.ExpiryTime.Sub(atTime) > 0
}
// Clone duplicates ru
func (ru *ResourceUsage) Clone() (cln *ResourceUsage) {
cln = new(ResourceUsage)
*cln = *ru
return
}
// Resource represents a resource in the system
// not thread safe, needs locking at process level
type Resource struct {
Tenant string
ID string
Usages map[string]*ResourceUsage
TTLIdx []string // holds ordered list of ResourceIDs based on their TTL, empty if feature is disabled
}
// Clone clones *Resource (lkID excluded)
func (r *Resource) Clone() *Resource {
if r == nil {
return nil
}
clone := &Resource{
Tenant: r.Tenant,
ID: r.ID,
}
if r.Usages != nil {
clone.Usages = make(map[string]*ResourceUsage, len(r.Usages))
for key, usage := range r.Usages {
clone.Usages[key] = usage.Clone()
}
}
if r.TTLIdx != nil {
clone.TTLIdx = make([]string, len(r.TTLIdx))
copy(clone.TTLIdx, r.TTLIdx)
}
return clone
}
// CacheClone returns a clone of Resource used by ltcache CacheCloner
func (r *Resource) CacheClone() any {
return r.Clone()
}
// ResourceWithAPIOpts is used in replicatorV1 for dispatcher
type ResourceWithAPIOpts struct {
*Resource
APIOpts map[string]any
}
// TenantID returns the unique ID in a multi-tenant environment
func (r *Resource) TenantID() string {
return ConcatenatedKey(r.Tenant, r.ID)
}
// TotalUsage returns the sum of all usage units
// Exported to be used in FilterS
func (r *Resource) TotalUsage() float64 {
var tu float64
for _, ru := range r.Usages {
tu += ru.Units
}
return tu
}
// AsMapStringInterface converts Resource struct to map[string]any
func (rp *Resource) AsMapStringInterface() map[string]any {
if rp == nil {
return nil
}
return map[string]any{
Tenant: rp.Tenant,
ID: rp.ID,
Usages: rp.Usages,
TTLIdx: rp.TTLIdx,
}
}
// MapStringInterfaceToResource converts map[string]any to Resource struct
func MapStringInterfaceToResource(m map[string]any) *Resource {
rp := &Resource{}
if v, ok := m[Tenant].(string); ok {
rp.Tenant = v
}
if v, ok := m[ID].(string); ok {
rp.ID = v
}
rp.Usages = InterfaceToMapStringResourceUsage(m[Usages])
rp.TTLIdx = InterfaceToStringSlice(m[TTLIdx])
return rp
}
// InterfaceToMapStringResourceUsage converts any to map[string]*ResourceUsage
func InterfaceToMapStringResourceUsage(v any) map[string]*ResourceUsage {
if v == nil {
return nil
}
switch val := v.(type) {
case map[string]*ResourceUsage:
return val
case map[string]any:
result := make(map[string]*ResourceUsage)
for k, v := range val {
if balMap, ok := v.(map[string]any); ok {
result[k] = MapStringInterfaceToResourceUsage(balMap)
} else if bal, ok := v.(*ResourceUsage); ok {
result[k] = bal
}
}
return result
}
return nil
}
// MapStringInterfaceToResourceUsage converts map[string]any to *ResourceUsage
func MapStringInterfaceToResourceUsage(m map[string]any) *ResourceUsage {
resUsage := &ResourceUsage{}
if v, ok := m[Tenant].(string); ok {
resUsage.Tenant = v
}
if v, ok := m[ID].(string); ok {
resUsage.ID = v
}
if v, ok := m[ExpiryTime].(string); ok {
if t, err := time.Parse(time.RFC3339, v); err == nil {
resUsage.ExpiryTime = t
}
}
if v, ok := m[Units].(float64); ok {
resUsage.Units = v
}
return resUsage
}
// Available returns the available number of units
// Exported method to be used by filterS
func (r *ResourceWithConfig) Available() float64 {
return r.Config.Limit - r.TotalUsage()
}
type ResourceWithConfig struct {
*Resource
Config *ResourceProfile
}
func (rp *ResourceProfile) Set(path []string, val any, _ bool) (err error) {
if len(path) != 1 {
return ErrWrongPath
}
switch path[0] {
default:
return ErrWrongPath
case Tenant:
rp.Tenant = IfaceAsString(val)
case ID:
rp.ID = IfaceAsString(val)
case FilterIDs:
var valA []string
valA, err = IfaceAsStringSlice(val)
rp.FilterIDs = append(rp.FilterIDs, valA...)
case UsageTTL:
rp.UsageTTL, err = IfaceAsDuration(val)
case Limit:
if val != EmptyString {
rp.Limit, err = IfaceAsFloat64(val)
}
case AllocationMessage:
rp.AllocationMessage = IfaceAsString(val)
case Blocker:
rp.Blocker, err = IfaceAsBool(val)
case Stored:
rp.Stored, err = IfaceAsBool(val)
case Weights:
if val != EmptyString {
rp.Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep)
}
case ThresholdIDs:
var valA []string
valA, err = IfaceAsStringSlice(val)
rp.ThresholdIDs = append(rp.ThresholdIDs, valA...)
}
return
}
func (rp *ResourceProfile) Merge(v2 any) {
vi := v2.(*ResourceProfile)
if len(vi.Tenant) != 0 {
rp.Tenant = vi.Tenant
}
if len(vi.ID) != 0 {
rp.ID = vi.ID
}
rp.FilterIDs = append(rp.FilterIDs, vi.FilterIDs...)
rp.ThresholdIDs = append(rp.ThresholdIDs, vi.ThresholdIDs...)
if len(vi.AllocationMessage) != 0 {
rp.AllocationMessage = vi.AllocationMessage
}
if vi.UsageTTL != 0 {
rp.UsageTTL = vi.UsageTTL
}
if vi.Limit != 0 {
rp.Limit = vi.Limit
}
if vi.Blocker {
rp.Blocker = vi.Blocker
}
if vi.Stored {
rp.Stored = vi.Stored
}
rp.Weights = append(rp.Weights, vi.Weights...)
}
func (rp *ResourceProfile) String() string { return ToJSON(rp) }
func (rp *ResourceProfile) FieldAsString(fldPath []string) (_ string, err error) {
var val any
if val, err = rp.FieldAsInterface(fldPath); err != nil {
return
}
return IfaceAsString(val), nil
}
func (rp *ResourceProfile) FieldAsInterface(fldPath []string) (_ any, err error) {
if len(fldPath) != 1 {
return nil, ErrNotFound
}
switch fldPath[0] {
default:
fld, idx := GetPathIndex(fldPath[0])
if idx != nil {
switch fld {
case ThresholdIDs:
if *idx < len(rp.ThresholdIDs) {
return rp.ThresholdIDs[*idx], nil
}
case FilterIDs:
if *idx < len(rp.FilterIDs) {
return rp.FilterIDs[*idx], nil
}
}
}
return nil, ErrNotFound
case Tenant:
return rp.Tenant, nil
case ID:
return rp.ID, nil
case FilterIDs:
return rp.FilterIDs, nil
case UsageTTL:
return rp.UsageTTL, nil
case Limit:
return rp.Limit, nil
case AllocationMessage:
return rp.AllocationMessage, nil
case Blocker:
return rp.Blocker, nil
case Stored:
return rp.Stored, nil
case Weights:
return rp.Weights, nil
case ThresholdIDs:
return rp.ThresholdIDs, nil
}
}
// ResourceProfileLockKey returns the ID used to lock a resourceProfile with guardian
func ResourceProfileLockKey(tnt, id string) string {
return ConcatenatedKey(CacheResourceProfiles, tnt, id)
}
// ResourceLockKey returns the ID used to lock a resource with guardian
func ResourceLockKey(tnt, id string) string {
return ConcatenatedKey(CacheResources, tnt, id)
}
// AsMapStringInterface converts ResourceProfile struct to map[string]any
func (rp *ResourceProfile) AsMapStringInterface() map[string]any {
if rp == nil {
return nil
}
return map[string]any{
Tenant: rp.Tenant,
ID: rp.ID,
FilterIDs: rp.FilterIDs,
UsageTTL: rp.UsageTTL,
Limit: rp.Limit,
AllocationMessage: rp.AllocationMessage,
Blocker: rp.Blocker,
Stored: rp.Stored,
Weights: rp.Weights,
ThresholdIDs: rp.ThresholdIDs,
}
}
// MapStringInterfaceToResourceProfile converts map[string]any to ResourceProfile struct
func MapStringInterfaceToResourceProfile(m map[string]any) (rp *ResourceProfile, err error) {
rp = &ResourceProfile{}
if v, ok := m[Tenant].(string); ok {
rp.Tenant = v
}
if v, ok := m[ID].(string); ok {
rp.ID = v
}
rp.FilterIDs = InterfaceToStringSlice(m[FilterIDs])
if v, ok := m[UsageTTL].(string); ok {
if dur, err := time.ParseDuration(v); err != nil {
return nil, err
} else {
rp.UsageTTL = dur
}
} else if v, ok := m[UsageTTL].(float64); ok { // for -1 cases
rp.UsageTTL = time.Duration(v)
}
if v, ok := m[Limit].(float64); ok {
rp.Limit = v
}
if v, ok := m[AllocationMessage].(string); ok {
rp.AllocationMessage = v
}
if v, ok := m[Blocker].(bool); ok {
rp.Blocker = v
}
if v, ok := m[Stored].(bool); ok {
rp.Stored = v
}
rp.Weights = InterfaceToDynamicWeights(m[Weights])
rp.ThresholdIDs = InterfaceToStringSlice(m[ThresholdIDs])
return
}