From 5fda30ca29d7238001da17908cdd2d184afa0ab2 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 22 May 2012 15:56:23 +0300 Subject: [PATCH] rater fallback --- cmd/cgr-loader/cgr-loader.go | 2 +- data/test.kch | Bin 6299128 -> 6299224 bytes data/test_activation_periods.json | 23 ++++++++++++ timespans/activationperiod_test.go | 54 +++++++++++++++++++++++++++-- timespans/calldesc.go | 51 ++++++++++++++++++++++----- timespans/calldesc_test.go | 6 ++-- timespans/storage_interface.go | 4 +-- timespans/storage_kyoto.go | 32 +++++++++++------ timespans/storage_mongo.go | 9 ++--- timespans/storage_redis.go | 26 +++++++++----- 10 files changed, 167 insertions(+), 40 deletions(-) diff --git a/cmd/cgr-loader/cgr-loader.go b/cmd/cgr-loader/cgr-loader.go index 46c04ecfc..72890e8ca 100644 --- a/cmd/cgr-loader/cgr-loader.go +++ b/cmd/cgr-loader/cgr-loader.go @@ -45,7 +45,7 @@ func writeToStorage(storage timespans.StorageGetter, tariffPlans []*timespans.TariffPlan, userBudgets []*timespans.UserBudget) { for _, cd := range callDescriptors { - storage.SetActivationPeriods(cd.GetKey(), cd.ActivationPeriods) + storage.SetActivationPeriodsOrFallback(cd.GetKey(), cd.ActivationPeriods, cd.FallbackKey) log.Printf("Storing activation periods for %q", cd.GetKey()) } for _, d := range destinations { diff --git a/data/test.kch b/data/test.kch index ded3f7961dc9d2d5b5cdf2f980087bd690bce220..41b96c6bc793a96d8a119cee773e9f6f5b366fa0 100644 GIT binary patch delta 730 zcmeydyMg(}o(AR#>YO4BAdnyz(P-Fe$hg&zDQO}L0}pdAg4^iO>cF_wfhl-mqr+AQ z#$_|xKl*ZR|LDtgr`OuGakX(=|Cm?p-Zdv5g z6~dSYQ;Xe22)i~q1l$6e-HE9_S+m`IVF(b1ZZ}^Twn84L$`4aj@WhP{J6eHqTao2} zv2{dtdxb>8MaFs-a6Cf^9*(kHAayv+ zGcrV0dWH>Ro+yOkRfS1{ZJsV8m7pzs2H8pmZX_iJAO)(bsFv}vBbfoUEi>)ic2}u{ G*GvFS%G?zI delta 642 zcmcbyr-Av$?gr)w>YRcMAdn#YqtUR{ka4RaQ_@5h1|H@N77%ypJqr+AQ z#@PAoAALEufArHl1LP zA9Vq0jzVbO=+JN4Zdqgx#12602*gf6?7ZEw$fYY}`^5KlE1IwyhRta^!hxozAWVnp zOxA2SUl;?CSsNM|LNr+$TN@f!znf^N1OPz|#-#uN diff --git a/data/test_activation_periods.json b/data/test_activation_periods.json index 8b4d86bbb..a21cfb14b 100644 --- a/data/test_activation_periods.json +++ b/data/test_activation_periods.json @@ -30,6 +30,29 @@ } ] }, +{"TOR": "0","CstmId":"vdf","Subject":"rif","DestinationPrefix":"0745", "FallbackKey": "vdf:radu"}, +{"TOR": "0","CstmId":"vdf","Subject":"rif","DestinationPrefix":"*", "FallbackKey": "vdf:radu"}, + +{"TOR": "0","CstmId":"vdf","Subject":"rif","DestinationPrefix":"0721", "FallbackKey": "vdf:radu"}, + +{"TOR": "0","CstmId":"vdf","Subject":"radu","DestinationPrefix":"0745", "ActivationPeriods": [ + {"ActivationTime": "2012-01-01T00:00:00Z", "Intervals": [ + {"BillingUnit":60,"ConnectFee":0,"Month":0,"MonthDay":0,"Ponder":0,"Price":1,"StartTime":"","EndTime":""} + ] + } + ] +}, + +{"TOR": "0","CstmId":"vdf","Subject":"radu","DestinationPrefix":"00", "ActivationPeriods": [ + {"ActivationTime": "2012-01-01T00:00:00Z", "Intervals": [ + {"BillingUnit":60,"ConnectFee":0,"Month":0,"MonthDay":0,"Ponder":0,"Price":1,"StartTime":"","EndTime":""} + ] + } + ] +}, + +{"TOR": "0","CstmId":"vdf","Subject":"radu","DestinationPrefix":"0721", "FallbackKey": "vdf:rif"}, + {"TOR": "0","CstmId":"1","Subject":"1000","DestinationPrefix":"0723", "ActivationPeriods": [ {"ActivationTime": "2012-01-01T00:00:00Z", "Intervals": [ {"BillingUnit":60,"ConnectFee":0,"Month":0,"MonthDay":0,"Ponder":0,"Price":1,"StartTime":"","EndTime":""} diff --git a/timespans/activationperiod_test.go b/timespans/activationperiod_test.go index a5f157c9c..588db6ca3 100644 --- a/timespans/activationperiod_test.go +++ b/timespans/activationperiod_test.go @@ -29,7 +29,10 @@ func TestApRestoreKyoto(t *testing.T) { getter, _ := NewKyotoStorage("../data/test.kch") defer getter.Close() - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", + Subject: "rif", + DestinationPrefix: "0257", + storageGetter: getter} cd.SearchStorageForPrefix() if len(cd.ActivationPeriods) != 2 { t.Error("Error restoring activation periods: ", cd.ActivationPeriods) @@ -40,7 +43,10 @@ func TestApRestoreRedis(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", + Subject: "rif", + DestinationPrefix: "0257", + storageGetter: getter} cd.SearchStorageForPrefix() if len(cd.ActivationPeriods) != 2 { t.Error("Error restoring activation periods: ", cd.ActivationPeriods) @@ -68,6 +74,50 @@ func TestApStoreRestore(t *testing.T) { } } +func TestFallbackDirect(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0745", storageGetter: getter} + cd.SearchStorageForPrefix() + if len(cd.ActivationPeriods) != 1 { + t.Error("Error restoring activation periods: ", cd.ActivationPeriods) + } +} + +func TestFallbackWithBackTrace(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0745121", storageGetter: getter} + cd.SearchStorageForPrefix() + if len(cd.ActivationPeriods) != 1 { + t.Error("Error restoring activation periods: ", cd.ActivationPeriods) + } +} + +func TestFallbackDefault(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "00000", storageGetter: getter} + cd.SearchStorageForPrefix() + if len(cd.ActivationPeriods) != 1 { + t.Error("Error restoring activation periods: ", cd.ActivationPeriods) + } +} + +func TestFallbackNoInfiniteLoop(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0721", storageGetter: getter} + cd.SearchStorageForPrefix() + if len(cd.ActivationPeriods) != 0 { + t.Error("Error restoring activation periods: ", cd.ActivationPeriods) + } +} + /**************************** Benchmarks *************************************/ func BenchmarkActivationPeriodRestore(b *testing.B) { diff --git a/timespans/calldesc.go b/timespans/calldesc.go index acf431a73..c824da0a8 100644 --- a/timespans/calldesc.go +++ b/timespans/calldesc.go @@ -19,6 +19,7 @@ along with this program. If not, see package timespans import ( + "errors" "fmt" "log" "math" @@ -27,7 +28,8 @@ import ( const ( // the minimum length for a destination prefix to be matched. - MinPrefixLength = 2 + MinPrefixLength = 2 + RecursionMaxDepth = 4 ) /* @@ -57,6 +59,7 @@ type CallDescriptor struct { Amount float64 FallbackSubject string // the subject to check for destination if not found on primary subject ActivationPeriods []*ActivationPeriod + FallbackKey string storageGetter StorageGetter userBudget *UserBudget } @@ -95,21 +98,51 @@ func (cd *CallDescriptor) SearchStorageForPrefix() (destPrefix string, err error base := fmt.Sprintf("%s:%s:", cd.CstmId, cd.Subject) destPrefix = cd.DestinationPrefix key := base + destPrefix - values, err := cd.storageGetter.GetActivationPeriods(key) - //get for a smaller prefix if the orignal one was not found - for i := len(cd.DestinationPrefix); err != nil && - i >= MinPrefixLength; values, err = cd.storageGetter.GetActivationPeriods(key) { - i-- - destPrefix = cd.DestinationPrefix[:i] - key = base + destPrefix + values, err := cd.getActivationPeriodsOrFallback(key, base, destPrefix, 1) + if err != nil { + key := base + "*" + values, err = cd.getActivationPeriodsOrFallback(key, base, destPrefix, 1) } //load the activation preriods - if err == nil { + if err == nil && len(values) > 0 { cd.ActivationPeriods = values } return } +func (cd *CallDescriptor) getActivationPeriodsOrFallback(key, base, destPrefix string, recursionDepth int) (values []*ActivationPeriod, err error) { + if recursionDepth > RecursionMaxDepth { + err = errors.New("Max fallback recursion depth reached!" + key) + log.Print(err) + return + } + values, fallbackKey, err := cd.storageGetter.GetActivationPeriodsOrFallback(key) + if fallbackKey != "" { + base = fallbackKey + ":" + key = base + destPrefix + recursionDepth++ + return cd.getActivationPeriodsOrFallback(key, base, destPrefix, recursionDepth) + } + //get for a smaller prefix if the orignal one was not found + for i := len(cd.DestinationPrefix); err != nil || fallbackKey != ""; { + if fallbackKey != "" { + base = fallbackKey + ":" + key = base + destPrefix + recursionDepth++ + return cd.getActivationPeriodsOrFallback(key, base, destPrefix, recursionDepth) + } + i-- + if i >= MinPrefixLength { + destPrefix = cd.DestinationPrefix[:i] + key = base + destPrefix + } else { + break + } + values, fallbackKey, err = cd.storageGetter.GetActivationPeriodsOrFallback(key) + } + return +} + /* Constructs the key for the storage lookup. The prefixLen is limiting the length of the destination prefix. diff --git a/timespans/calldesc_test.go b/timespans/calldesc_test.go index fe453e8bd..6a21500c2 100644 --- a/timespans/calldesc_test.go +++ b/timespans/calldesc_test.go @@ -274,7 +274,7 @@ func BenchmarkRedisGetting(b *testing.B) { cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { - getter.GetActivationPeriods(cd.GetKey()) + getter.GetActivationPeriodsOrFallback(cd.GetKey()) } } @@ -317,7 +317,7 @@ func BenchmarkKyotoGetting(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { key := cd.GetKey() - getter.GetActivationPeriods(key) + getter.GetActivationPeriodsOrFallback(key) } } @@ -374,7 +374,7 @@ func BenchmarkMongoGetting(b *testing.B) { cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { - getter.GetActivationPeriods(cd.GetKey()) + getter.GetActivationPeriodsOrFallback(cd.GetKey()) } } diff --git a/timespans/storage_interface.go b/timespans/storage_interface.go index 5087d77a7..4eac4cc5d 100644 --- a/timespans/storage_interface.go +++ b/timespans/storage_interface.go @@ -23,8 +23,8 @@ Interface for storage providers. */ type StorageGetter interface { Close() - GetActivationPeriods(string) ([]*ActivationPeriod, error) - SetActivationPeriods(string, []*ActivationPeriod) error + GetActivationPeriodsOrFallback(string) ([]*ActivationPeriod, string, error) + SetActivationPeriodsOrFallback(string, []*ActivationPeriod, string) error GetDestination(string) (*Destination, error) SetDestination(*Destination) error GetTariffPlan(string) (*TariffPlan, error) diff --git a/timespans/storage_kyoto.go b/timespans/storage_kyoto.go index b5d99bf84..054e562ce 100644 --- a/timespans/storage_kyoto.go +++ b/timespans/storage_kyoto.go @@ -22,7 +22,7 @@ import ( //"github.com/fsouza/gokabinet/kc" "bitbucket.org/ww/cabinet" //"log" - "bytes" + "strings" ) type KyotoStorage struct { @@ -40,25 +40,35 @@ func (ks *KyotoStorage) Close() { ks.db.Close() } -func (ks *KyotoStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { - values, err := ks.db.Get([]byte(key)) - - if err == nil { - for _, ap_string := range bytes.Split(values, []byte{'\n'}) { +func (ks *KyotoStorage) GetActivationPeriodsOrFallback(key string) (aps []*ActivationPeriod, fallbackKey string, err error) { + valuesBytes, err := ks.db.Get([]byte(key)) + if err != nil { + return + } + valuesString := string(valuesBytes) + values := strings.Split(valuesString, "\n") + if len(values) > 1 { + for _, ap_string := range values { if len(ap_string) > 0 { ap := &ActivationPeriod{} - ap.restore(string(ap_string)) + ap.restore(ap_string) aps = append(aps, ap) } } + } else { // fallback case + fallbackKey = valuesString } - return aps, err + return } -func (ks *KyotoStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { +func (ks *KyotoStorage) SetActivationPeriodsOrFallback(key string, aps []*ActivationPeriod, fallbackKey string) error { result := "" - for _, ap := range aps { - result += ap.store() + "\n" + if len(aps) > 0 { + for _, ap := range aps { + result += ap.store() + "\n" + } + } else { + result = fallbackKey } return ks.db.Set([]byte(key), []byte(result)) } diff --git a/timespans/storage_mongo.go b/timespans/storage_mongo.go index c066e2420..0d0dbc1c0 100644 --- a/timespans/storage_mongo.go +++ b/timespans/storage_mongo.go @@ -56,19 +56,20 @@ Helper type for activation periods storage. */ type KeyValue struct { Key string + FallbackKey string ActivationPeriods []*ActivationPeriod } -func (ms *MongoStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { +func (ms *MongoStorage) GetActivationPeriodsOrFallback(key string) (aps []*ActivationPeriod, fallbackKey string, err error) { ndb := ms.db.C("activationPeriods") result := KeyValue{} err = ndb.Find(bson.M{"key": key}).One(&result) - return result.ActivationPeriods, err + return result.ActivationPeriods, result.FallbackKey, err } -func (ms *MongoStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { +func (ms *MongoStorage) SetActivationPeriodsOrFallback(key string, aps []*ActivationPeriod, fallbackKey string) error { ndb := ms.db.C("activationPeriods") - return ndb.Insert(&KeyValue{key, aps}) + return ndb.Insert(&KeyValue{key, fallbackKey, aps}) } func (ms *MongoStorage) GetDestination(key string) (result *Destination, err error) { diff --git a/timespans/storage_redis.go b/timespans/storage_redis.go index edaef1b87..a3990ca9d 100644 --- a/timespans/storage_redis.go +++ b/timespans/storage_redis.go @@ -37,27 +37,37 @@ func (rs *RedisStorage) Close() { rs.db.Quit() } -func (rs *RedisStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { +func (rs *RedisStorage) GetActivationPeriodsOrFallback(key string) (aps []*ActivationPeriod, fallbackKey string, err error) { //rs.db.Select(rs.dbNb) elem, err := rs.db.Get(key) - values := elem.String() - if err == nil { - for _, ap_string := range strings.Split(values, "\n") { + if err != nil { + return + } + valuesString := elem.String() + values := strings.Split(valuesString, "\n") + if len(values) > 1 { + for _, ap_string := range values { if len(ap_string) > 0 { ap := &ActivationPeriod{} ap.restore(ap_string) aps = append(aps, ap) } } + } else { // fallback case + fallbackKey = valuesString } - return aps, err + return } -func (rs *RedisStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { +func (rs *RedisStorage) SetActivationPeriodsOrFallback(key string, aps []*ActivationPeriod, fallbackKey string) error { //.db.Select(rs.dbNb) result := "" - for _, ap := range aps { - result += ap.store() + "\n" + if len(aps) > 0 { + for _, ap := range aps { + result += ap.store() + "\n" + } + } else { + result = fallbackKey } return rs.db.Set(key, result) }