mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-15 21:29:52 +05:00
workin on rating profiles in db
This commit is contained in:
@@ -97,34 +97,3 @@ func (ap *ActivationPeriod) restore(input string) {
|
||||
ap.Intervals = append(ap.Intervals, i)
|
||||
}
|
||||
}
|
||||
|
||||
type RatingProfile struct {
|
||||
Id string `bson:"_id,omitempty"`
|
||||
DestinationInfo string
|
||||
FallbackKey string
|
||||
ActivationPeriods []*ActivationPeriod
|
||||
}
|
||||
|
||||
func (rp *RatingProfile) store() (result string) {
|
||||
result += rp.Id + ">"
|
||||
result += rp.DestinationInfo + ">"
|
||||
result += rp.FallbackKey + ">"
|
||||
for _, ap := range rp.ActivationPeriods {
|
||||
result += ap.store() + "<"
|
||||
}
|
||||
result = strings.TrimRight(result, "<")
|
||||
return
|
||||
}
|
||||
|
||||
func (rp *RatingProfile) restore(input string) {
|
||||
elements := strings.Split(input, ">")
|
||||
rp.Id = elements[0]
|
||||
rp.DestinationInfo = elements[1]
|
||||
rp.FallbackKey = elements[2]
|
||||
apsList := strings.Split(elements[3], "<")
|
||||
for _, aps := range apsList {
|
||||
ap := new(ActivationPeriod)
|
||||
ap.restore(aps)
|
||||
rp.ActivationPeriods = append(rp.ActivationPeriods, ap)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestApRestoreFromStorage(t *testing.T) {
|
||||
Tenant: "CUSTOMER_1",
|
||||
Subject: "rif:from:tm",
|
||||
Destination: "49"}
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 2 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func TestApStoreRestoreBlank(t *testing.T) {
|
||||
|
||||
func TestFallbackDirect(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "41"}
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func TestFallbackDirect(t *testing.T) {
|
||||
|
||||
func TestFallbackWithBackTrace(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "4123"}
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func TestFallbackWithBackTrace(t *testing.T) {
|
||||
|
||||
func TestFallbackDefault(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "4123"}
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func TestFallbackDefault(t *testing.T) {
|
||||
|
||||
func TestFallbackNoInfiniteLoop(t *testing.T) {
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0721"}
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 0 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
}
|
||||
|
||||
@@ -36,11 +36,8 @@ func init() {
|
||||
}
|
||||
|
||||
const (
|
||||
// the minimum length for a destination prefix to be matched.
|
||||
MinPrefixLength = 2
|
||||
RecursionMaxDepth = 4
|
||||
FallbackDestination = "fallback" // the string to be used to mark the fallback destination
|
||||
FallbackSubject = "*all"
|
||||
RECURSION_MAX_DEPTH = 4
|
||||
FALLBACK_SUBJECT = "*all"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -80,7 +77,6 @@ type CallDescriptor struct {
|
||||
Amount float64
|
||||
FallbackSubject string // the subject to check for destination if not found on primary subject
|
||||
ActivationPeriods []*ActivationPeriod
|
||||
FallbackKey string
|
||||
userBalance *UserBalance
|
||||
}
|
||||
|
||||
@@ -89,22 +85,6 @@ func (cd *CallDescriptor) AddActivationPeriod(aps ...*ActivationPeriod) {
|
||||
cd.ActivationPeriods = append(cd.ActivationPeriods, aps...)
|
||||
}
|
||||
|
||||
// Adds an activation period that applyes to current call descriptor if not already present.
|
||||
func (cd *CallDescriptor) AddActivationPeriodIfNotPresent(aps ...*ActivationPeriod) {
|
||||
for _, ap := range aps {
|
||||
found := false
|
||||
for _, eap := range cd.ActivationPeriods {
|
||||
if ap.Equal(eap) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
cd.ActivationPeriods = append(cd.ActivationPeriods, ap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the key used to retrive the user balance involved in this call
|
||||
func (cd *CallDescriptor) GetUserBalanceKey() string {
|
||||
subj := cd.Subject
|
||||
@@ -148,31 +128,18 @@ func SetDebitPeriod(d time.Duration) {
|
||||
/*
|
||||
Restores the activation periods for the specified prefix from storage.
|
||||
*/
|
||||
func (cd *CallDescriptor) SearchStorageForPrefix() (destPrefix string, err error) {
|
||||
func (cd *CallDescriptor) LoadActivationPeriods() (destPrefix string, err error) {
|
||||
if val, err := cache.GetXCached(cd.GetKey()); err == nil {
|
||||
xaps := val.(xCachedActivationPeriods)
|
||||
cd.ActivationPeriods = xaps.aps
|
||||
return xaps.destPrefix, nil
|
||||
}
|
||||
cd.ActivationPeriods = make([]*ActivationPeriod, 0)
|
||||
base := fmt.Sprintf("%s:%s:%s:%s:", cd.Direction, cd.Tenant, cd.TOR, cd.Subject)
|
||||
destPrefix = cd.Destination
|
||||
key := base + destPrefix
|
||||
values, err := cd.getActivationPeriodsOrFallback(key, base, destPrefix, 1)
|
||||
destPrefix, values, err := cd.getActivationPeriodsForPrefix(cd.GetKey(), cd.Destination, 1)
|
||||
if err != nil {
|
||||
// use the default destination
|
||||
key := base + FallbackDestination
|
||||
values, err = cd.getActivationPeriodsOrFallback(key, base, destPrefix, 1)
|
||||
}
|
||||
if err != nil {
|
||||
// use the default subject
|
||||
base = fmt.Sprintf("%s:%s:%s:%s:", cd.Direction, cd.Tenant, cd.TOR, FallbackSubject)
|
||||
key = base + destPrefix
|
||||
values, err = cd.getActivationPeriodsOrFallback(key, base, destPrefix, 1)
|
||||
if err != nil {
|
||||
// use the default destination
|
||||
key := base + FallbackDestination
|
||||
values, err = cd.getActivationPeriodsOrFallback(key, base, destPrefix, 1)
|
||||
fallbackKey := fmt.Sprintf("%s:%s:%s", cd.Direction, cd.Tenant, cd.TOR, FALLBACK_SUBJECT)
|
||||
// use the default subject
|
||||
destPrefix, values, err = cd.getActivationPeriodsForPrefix(fallbackKey, cd.Destination, 1)
|
||||
}
|
||||
}
|
||||
//load the activation preriods
|
||||
@@ -184,37 +151,23 @@ func (cd *CallDescriptor) SearchStorageForPrefix() (destPrefix string, err error
|
||||
return
|
||||
}
|
||||
|
||||
func (cd *CallDescriptor) getRatinPprofile(key, base, destPrefix string, recursionDepth int) (values []*ActivationPeriod, err error) {
|
||||
if recursionDepth > RecursionMaxDepth {
|
||||
func (cd *CallDescriptor) getActivationPeriodsForPrefix(key, prefix string, recursionDepth int) (foundPrefix string, aps []*ActivationPeriod, err error) {
|
||||
if recursionDepth > RECURSION_MAX_DEPTH {
|
||||
err = errors.New("Max fallback recursion depth reached!" + key)
|
||||
return
|
||||
}
|
||||
rp, err := storageGetter.GetRatingProfile(key)
|
||||
if rp.FallbackKey != "" {
|
||||
base = rp.FallbackKey + ":"
|
||||
key = base + destPrefix
|
||||
recursionDepth++
|
||||
return cd.getRatingProfile(key, base, destPrefix, recursionDepth)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
//get for a smaller prefix if the orignal one was not found
|
||||
for i := len(cd.Destination); err != nil || fallbackKey != ""; {
|
||||
if fallbackKey != "" {
|
||||
base = fallbackKey + ":"
|
||||
key = base + destPrefix
|
||||
foundPrefix, aps, err = rp.GetActivationPeriodsForPrefix(prefix)
|
||||
if err != nil {
|
||||
if rp.FallbackKey != "" {
|
||||
recursionDepth++
|
||||
return cd.getRatingProfile(key, base, destPrefix, recursionDepth)
|
||||
return cd.getActivationPeriodsForPrefix(rp.FallbackKey, prefix, recursionDepth)
|
||||
}
|
||||
|
||||
i--
|
||||
if i >= MinPrefixLength {
|
||||
destPrefix = cd.Destination[:i]
|
||||
key = base + destPrefix
|
||||
} else {
|
||||
break
|
||||
}
|
||||
values, fallbackKey, err = storageGetter.GetRatingProfile(key)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -306,7 +259,7 @@ func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeS
|
||||
Creates a CallCost structure with the cost information calculated for the received CallDescriptor.
|
||||
*/
|
||||
func (cd *CallDescriptor) GetCost() (*CallCost, error) {
|
||||
destPrefix, err := cd.SearchStorageForPrefix()
|
||||
destPrefix, err := cd.LoadActivationPeriods()
|
||||
timespans := cd.splitInTimeSpans()
|
||||
cost := 0.0
|
||||
connectionFee := 0.0
|
||||
@@ -336,7 +289,7 @@ and will decrease it by 10% for nine times. So if the user has little credit it
|
||||
If the user has no credit then it will return 0.
|
||||
*/
|
||||
func (cd *CallDescriptor) GetMaxSessionTime() (seconds float64, err error) {
|
||||
_, err = cd.SearchStorageForPrefix()
|
||||
_, err = cd.LoadActivationPeriods()
|
||||
now := time.Now()
|
||||
availableCredit, availableSeconds := 0.0, 0.0
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestSplitSpans(t *testing.T) {
|
||||
t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
timespans := cd.splitInTimeSpans()
|
||||
if len(timespans) != 2 {
|
||||
t.Log(cd.ActivationPeriods)
|
||||
@@ -70,7 +70,7 @@ func TestRedisSplitSpans(t *testing.T) {
|
||||
t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2}
|
||||
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
timespans := cd.splitInTimeSpans()
|
||||
if len(timespans) != 2 {
|
||||
t.Log(cd.ActivationPeriods)
|
||||
@@ -215,22 +215,6 @@ func TestMaxSessionTimeNoCredit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApAddAPIfNotPresent(t *testing.T) {
|
||||
ap1 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)}
|
||||
ap2 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)}
|
||||
ap3 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 1, time.UTC)}
|
||||
cd := &CallDescriptor{}
|
||||
cd.AddActivationPeriodIfNotPresent(ap1)
|
||||
cd.AddActivationPeriodIfNotPresent(ap2)
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Wronfully appended activation period ;)", len(cd.ActivationPeriods))
|
||||
}
|
||||
cd.AddActivationPeriodIfNotPresent(ap3)
|
||||
if len(cd.ActivationPeriods) != 2 {
|
||||
t.Error("Wronfully not appended activation period ;)", len(cd.ActivationPeriods))
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************** BENCHMARKS ***************************************/
|
||||
func BenchmarkStorageGetting(b *testing.B) {
|
||||
b.StopTimer()
|
||||
@@ -239,7 +223,7 @@ func BenchmarkStorageGetting(b *testing.B) {
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
storageGetter.GetActivationPeriodsOrFallback(cd.GetKey())
|
||||
storageGetter.GetRatingProfile(cd.GetKey())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +234,7 @@ func BenchmarkStorageRestoring(b *testing.B) {
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +254,7 @@ func BenchmarkSplitting(b *testing.B) {
|
||||
t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd.SearchStorageForPrefix()
|
||||
cd.LoadActivationPeriods()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.splitInTimeSpans()
|
||||
|
||||
@@ -39,7 +39,7 @@ type CSVReader struct {
|
||||
rates map[string][]*Rate
|
||||
timings map[string][]*Timing
|
||||
activationPeriods map[string]*ActivationPeriod
|
||||
ratingProfiles map[string]CallDescriptors
|
||||
ratingProfiles map[string]*RatingProfile
|
||||
}
|
||||
|
||||
func NewFileCSVReader() *CSVReader {
|
||||
@@ -50,7 +50,7 @@ func NewFileCSVReader() *CSVReader {
|
||||
c.rates = make(map[string][]*Rate)
|
||||
c.timings = make(map[string][]*Timing)
|
||||
c.activationPeriods = make(map[string]*ActivationPeriod)
|
||||
c.ratingProfiles = make(map[string]CallDescriptors)
|
||||
c.ratingProfiles = make(map[string]*RatingProfile)
|
||||
c.readerFunc = openFileCSVReader
|
||||
return c
|
||||
}
|
||||
@@ -98,15 +98,13 @@ func (csvr *CSVReader) WriteToDatabase(storage StorageGetter, flush, verbose boo
|
||||
if verbose {
|
||||
log.Print("Rating profiles")
|
||||
}
|
||||
for _, cds := range csvr.ratingProfiles {
|
||||
for _, cd := range cds {
|
||||
err = storage.SetActivationPeriodsOrFallback(cd.GetKey(), cd.ActivationPeriods, cd.FallbackKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if verbose {
|
||||
log.Print(cd.GetKey())
|
||||
}
|
||||
for _, rp := range csvr.ratingProfiles {
|
||||
err = storage.SetRatingProfile(rp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if verbose {
|
||||
log.Print(rp.Id)
|
||||
}
|
||||
}
|
||||
if verbose {
|
||||
@@ -283,46 +281,24 @@ func (csvr *CSVReader) LoadRatingProfiles(fn string, comma rune) (err error) {
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Cannot parse activation time from %v", record[6]))
|
||||
}
|
||||
key := fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, subject)
|
||||
rp, ok := csvr.ratingProfiles[key]
|
||||
if !ok {
|
||||
rp = &RatingProfile{Id: key}
|
||||
csvr.ratingProfiles[key] = rp
|
||||
}
|
||||
for _, d := range csvr.destinations {
|
||||
for _, p := range d.Prefixes { //destinations
|
||||
// Search for a CallDescriptor with the same key
|
||||
var cd *CallDescriptor
|
||||
key := fmt.Sprintf("%s:%s:%s:%s:%s", direction, tenant, tor, subject, p)
|
||||
for _, c := range csvr.ratingProfiles[p] {
|
||||
if c.GetKey() == key {
|
||||
cd = c
|
||||
}
|
||||
}
|
||||
if cd == nil {
|
||||
cd = &CallDescriptor{
|
||||
Direction: direction,
|
||||
Tenant: tenant,
|
||||
TOR: tor,
|
||||
Subject: subject,
|
||||
Destination: p,
|
||||
}
|
||||
csvr.ratingProfiles[p] = append(csvr.ratingProfiles[p], cd)
|
||||
}
|
||||
for _, p := range d.Prefixes { //destinations
|
||||
ap, exists := csvr.activationPeriods[record[5]]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not load ratinTiming for tag: ", record[5]))
|
||||
}
|
||||
newAP := &ActivationPeriod{}
|
||||
newAP := &ActivationPeriod{ActivationTime: at}
|
||||
//copy(newAP.Intervals, ap.Intervals)
|
||||
newAP.Intervals = append(newAP.Intervals, ap.Intervals...)
|
||||
newAP.ActivationTime = at
|
||||
cd.AddActivationPeriodIfNotPresent(newAP)
|
||||
if fallbacksubject != "" &&
|
||||
csvr.ratingProfiles[p].getKey(fmt.Sprintf("%s:%s:%s:%s:%s", direction, tenant, tor, subject, FallbackDestination)) == nil {
|
||||
cd = &CallDescriptor{
|
||||
Direction: direction,
|
||||
Tenant: tenant,
|
||||
TOR: tor,
|
||||
Subject: subject,
|
||||
Destination: FallbackDestination,
|
||||
FallbackKey: fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fallbacksubject),
|
||||
}
|
||||
csvr.ratingProfiles[p] = append(csvr.ratingProfiles[p], cd)
|
||||
rp.AddActivationPeriodIfNotPresent(p, newAP)
|
||||
if fallbacksubject != "" {
|
||||
rp.FallbackKey = fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fallbacksubject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,17 +118,6 @@ func (rt *RateTiming) GetInterval(r *Rate) (i *Interval) {
|
||||
return
|
||||
}
|
||||
|
||||
type CallDescriptors []*CallDescriptor
|
||||
|
||||
func (cds CallDescriptors) getKey(key string) *CallDescriptor {
|
||||
for _, cd := range cds {
|
||||
if cd.GetKey() == key {
|
||||
return cd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateCSVData(fn string, re *regexp.Regexp) (err error) {
|
||||
fin, err := os.Open(fn)
|
||||
if err != nil {
|
||||
|
||||
@@ -129,7 +129,7 @@ func TestLoadRateTimings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadRatingProfiles(t *testing.T) {
|
||||
if len(csvr.ratingProfiles) != 7 {
|
||||
if len(csvr.ratingProfiles) != 6 {
|
||||
t.Error("Failed to load rating profiles: ", len(csvr.ratingProfiles), csvr.ratingProfiles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (d *Destination) containsPrefix(prefix string) (bool, int) {
|
||||
if d == nil {
|
||||
return false, 0
|
||||
}
|
||||
for i := len(prefix); i >= MinPrefixLength; {
|
||||
for i := len(prefix); i >= MIN_PREFIX_LENGTH; {
|
||||
for _, p := range d.Prefixes {
|
||||
if p == prefix[:i] {
|
||||
return true, i
|
||||
|
||||
105
timespans/ratingprofile.go
Normal file
105
timespans/ratingprofile.go
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2012 Radu Ioan Fericean
|
||||
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package timespans
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// the minimum length for a destination prefix to be matched.
|
||||
MIN_PREFIX_LENGTH = 2
|
||||
)
|
||||
|
||||
type RatingProfile struct {
|
||||
Id string `bson:"_id,omitempty"`
|
||||
FallbackKey string
|
||||
DestinationMap map[string][]*ActivationPeriod
|
||||
}
|
||||
|
||||
func (rp *RatingProfile) store() (result string) {
|
||||
result += rp.FallbackKey + ">"
|
||||
for k, aps := range rp.DestinationMap {
|
||||
result += k + "="
|
||||
for _, ap := range aps {
|
||||
result += ap.store() + "<"
|
||||
}
|
||||
result = strings.TrimRight(result, "<")
|
||||
result += ">"
|
||||
}
|
||||
result = strings.TrimRight(result, ">")
|
||||
return
|
||||
}
|
||||
|
||||
func (rp *RatingProfile) restore(input string) {
|
||||
if rp.DestinationMap == nil {
|
||||
rp.DestinationMap = make(map[string][]*ActivationPeriod, 1)
|
||||
}
|
||||
elements := strings.Split(input, ">")
|
||||
rp.FallbackKey = elements[0]
|
||||
for _, kv := range elements[1:] {
|
||||
pair := strings.SplitN(kv, "=", 2)
|
||||
apList := strings.Split(pair[1], "<")
|
||||
var newAps []*ActivationPeriod
|
||||
for _, aps := range apList {
|
||||
ap := new(ActivationPeriod)
|
||||
ap.restore(aps)
|
||||
newAps = append(newAps, ap)
|
||||
}
|
||||
rp.DestinationMap[pair[0]] = newAps
|
||||
}
|
||||
}
|
||||
|
||||
// Adds an activation period that applyes to current rating profile if not already present.
|
||||
func (rp *RatingProfile) AddActivationPeriodIfNotPresent(destInfo string, aps ...*ActivationPeriod) {
|
||||
if rp.DestinationMap == nil {
|
||||
rp.DestinationMap = make(map[string][]*ActivationPeriod, 1)
|
||||
}
|
||||
for _, ap := range aps {
|
||||
found := false
|
||||
for _, eap := range rp.DestinationMap[destInfo] {
|
||||
if ap.Equal(eap) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
rp.DestinationMap[destInfo] = append(rp.DestinationMap[destInfo], ap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rp *RatingProfile) GetActivationPeriodsForPrefix(destPrefix string) (foundPrefix string, aps []*ActivationPeriod, err error) {
|
||||
var found = false
|
||||
foundPrefix = destPrefix
|
||||
for i := len(foundPrefix); !found; {
|
||||
if i >= MIN_PREFIX_LENGTH {
|
||||
foundPrefix = foundPrefix[:i]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
aps, found = rp.DestinationMap[foundPrefix]
|
||||
i--
|
||||
}
|
||||
if !found {
|
||||
return "", nil, errors.New("not found")
|
||||
}
|
||||
return
|
||||
}
|
||||
64
timespans/ratingprofile_test.go
Normal file
64
timespans/ratingprofile_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2012 Radu Ioan Fericean
|
||||
|
||||
This program is free software: you can Storagetribute 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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package timespans
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRpStoreRestore(t *testing.T) {
|
||||
d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC)
|
||||
i := &Interval{
|
||||
Months: Months{time.February},
|
||||
MonthDays: MonthDays{1},
|
||||
WeekDays: []time.Weekday{time.Wednesday, time.Thursday},
|
||||
StartTime: "14:30:00",
|
||||
EndTime: "15:00:00"}
|
||||
ap := &ActivationPeriod{ActivationTime: d}
|
||||
ap.AddInterval(i)
|
||||
rp := &RatingProfile{FallbackKey: "test"}
|
||||
rp.AddActivationPeriodIfNotPresent("0723", ap)
|
||||
result := rp.store()
|
||||
expected := "test>0723=1328106601000000000|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0"
|
||||
if result != expected {
|
||||
t.Errorf("Expected %q was %q", expected, result)
|
||||
}
|
||||
ap1 := ActivationPeriod{}
|
||||
ap1.restore(result)
|
||||
if reflect.DeepEqual(ap, ap1) {
|
||||
t.Errorf("Expected %v was %v", ap, ap1)
|
||||
}
|
||||
}
|
||||
func TestRpAddAPIfNotPresent(t *testing.T) {
|
||||
ap1 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)}
|
||||
ap2 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)}
|
||||
ap3 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 1, time.UTC)}
|
||||
rp := &RatingProfile{}
|
||||
rp.AddActivationPeriodIfNotPresent("test", ap1)
|
||||
rp.AddActivationPeriodIfNotPresent("test", ap2)
|
||||
if len(rp.DestinationMap["test"]) != 1 {
|
||||
t.Error("Wronfully appended activation period ;)", len(rp.DestinationMap["test"]))
|
||||
}
|
||||
rp.AddActivationPeriodIfNotPresent("test", ap3)
|
||||
if len(rp.DestinationMap["test"]) != 2 {
|
||||
t.Error("Wronfully not appended activation period ;)", len(rp.DestinationMap["test"]))
|
||||
}
|
||||
}
|
||||
@@ -120,13 +120,13 @@ func (mm *MyMarshaler) Marshal(v interface{}) (data []byte, err error) {
|
||||
case []*Action:
|
||||
result := ""
|
||||
for _, a := range v.([]*Action) {
|
||||
result += a.store() + "\n"
|
||||
result += a.store() + "+"
|
||||
}
|
||||
return []byte(result), nil
|
||||
case []*ActionTiming:
|
||||
result := ""
|
||||
for _, at := range v.([]*ActionTiming) {
|
||||
result += at.store() + "\n"
|
||||
result += at.store() + "+"
|
||||
}
|
||||
return []byte(result), nil
|
||||
case storer:
|
||||
@@ -144,7 +144,7 @@ func (mm *MyMarshaler) Unmarshal(data []byte, v interface{}) (err error) {
|
||||
switch v.(type) {
|
||||
case *[]*Action:
|
||||
as := v.(*[]*Action)
|
||||
for _, a_string := range strings.Split(string(data), "\n") {
|
||||
for _, a_string := range strings.Split(string(data), "+") {
|
||||
if len(a_string) > 0 {
|
||||
a := &Action{}
|
||||
a.restore(a_string)
|
||||
@@ -154,7 +154,7 @@ func (mm *MyMarshaler) Unmarshal(data []byte, v interface{}) (err error) {
|
||||
return nil
|
||||
case *[]*ActionTiming:
|
||||
ats := v.(*[]*ActionTiming)
|
||||
for _, at_string := range strings.Split(string(data), "\n") {
|
||||
for _, at_string := range strings.Split(string(data), "+") {
|
||||
if len(at_string) > 0 {
|
||||
at := &ActionTiming{}
|
||||
at.restore(at_string)
|
||||
|
||||
@@ -52,7 +52,7 @@ func (ms *MapStorage) GetRatingProfile(key string) (rp *RatingProfile, err error
|
||||
}
|
||||
|
||||
func (ms *MapStorage) SetRatingProfile(rp *RatingProfile) (err error) {
|
||||
result, err := ms.ms.Marshal(rp.Id)
|
||||
result, err := ms.ms.Marshal(rp)
|
||||
ms.dict[rp.Id] = result
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) SetDestination(dest *Destination) (err error) {
|
||||
result, err := rs.ms.Marshal(dest)
|
||||
return rs.db.Set(dest.Id, result)
|
||||
|
||||
Reference in New Issue
Block a user