diff --git a/cmd/loader/loader.go b/cmd/loader/loader.go index 06c7b6dc3..40103a2b0 100644 --- a/cmd/loader/loader.go +++ b/cmd/loader/loader.go @@ -16,11 +16,15 @@ var ( mongoserver = flag.String("mongoserver", "127.0.0.1:27017", "mongo server address (127.0.0.1:27017)") mongodb = flag.String("mdb", "test", "mongo database name (test)") redispass = flag.String("pass", "", "redis database password") - apfile = flag.String("apfile", "ap.json", "Activation Periods containing intervals file") - destfile = flag.String("destfile", "dest.json", "Destinations file") + apfile = flag.String("apfile", "ap.json", "Activation Periods containing intervals file") + destfile = flag.String("destfile", "dest.json", "Destinations file") + tpfile = flag.String("tpfile", "tp.json", "Tariff plans file") ) -func writeToStorage(storage timespans.StorageGetter,callDescriptors []*timespans.CallDescriptor,destinations []*timespans.Destination){ +func writeToStorage(storage timespans.StorageGetter, + callDescriptors []*timespans.CallDescriptor, + destinations []*timespans.Destination, + tariffPlans []*timespans.TariffPlan) { for _, cd := range callDescriptors { storage.SetActivationPeriods(cd.GetKey(), cd.ActivationPeriods) log.Printf("Storing %q", cd.GetKey()) @@ -29,18 +33,23 @@ func writeToStorage(storage timespans.StorageGetter,callDescriptors []*timespans storage.SetDestination(d) log.Printf("Storing %q", d.Id) } + for _, tp := range tariffPlans { + storage.SetTariffPlan(tp) + log.Printf("Storing %q", tp.Id) + } } func main() { flag.Parse() - log.Print("Reading from ", *apfile, *destfile) // reading activation periods fin, err := os.Open(*apfile) - if err != nil {log.Print("Cannot open activation periods input file", err)} + if err != nil { + log.Print("Cannot open activation periods input file", err) + } dec := json.NewDecoder(fin) @@ -54,7 +63,9 @@ func main() { // reading destinations fin, err = os.Open(*destfile) - if err != nil {log.Print("Cannot open destinations input file", err)} + if err != nil { + log.Print("Cannot open destinations input file", err) + } dec = json.NewDecoder(fin) @@ -65,19 +76,35 @@ func main() { } fin.Close() + // reading triff plans + fin, err = os.Open(*tpfile) + + if err != nil { + log.Print("Cannot open tariff plans input file", err) + } + + dec = json.NewDecoder(fin) + + var tariffPlans []*timespans.TariffPlan + if err := dec.Decode(&tariffPlans); err != nil { + log.Println(err) + return + } + fin.Close() + switch *storage { case "kyoto": storage, _ := timespans.NewKyotoStorage(*kyotofile) defer storage.Close() - writeToStorage(storage, callDescriptors, destinations) + writeToStorage(storage, callDescriptors, destinations, tariffPlans) case "mongo": storage, _ := timespans.NewMongoStorage("127.0.0.1", "test") defer storage.Close() - writeToStorage(storage, callDescriptors, destinations) + writeToStorage(storage, callDescriptors, destinations, tariffPlans) default: storage, _ := timespans.NewRedisStorage(*redisserver, *redisdb) defer storage.Close() - writeToStorage(storage, callDescriptors, destinations) + writeToStorage(storage, callDescriptors, destinations, tariffPlans) } } diff --git a/timespans/activationperiod.go b/timespans/activationperiod.go index 7ba6d40dd..6e356df61 100644 --- a/timespans/activationperiod.go +++ b/timespans/activationperiod.go @@ -28,9 +28,9 @@ func (ap *ActivationPeriod) AddInterval(is ...*Interval) { Serializes the activation periods for the storage. Used for key-value storages. */ func (ap *ActivationPeriod) store() (result string) { - result += strconv.FormatInt(ap.ActivationTime.UnixNano(), 10) + ";" - var is string + result += strconv.FormatInt(ap.ActivationTime.UnixNano(), 10) + ";" for _, i := range ap.Intervals { + var is string is = strconv.Itoa(int(i.Month)) + "|" is += strconv.Itoa(i.MonthDay) + "|" for _, wd := range i.WeekDays { diff --git a/timespans/destinations.go b/timespans/destinations.go index 4535b5302..86c4fb11d 100644 --- a/timespans/destinations.go +++ b/timespans/destinations.go @@ -24,3 +24,15 @@ func (d *Destination) store() (result string) { func (d *Destination) restore(input string) { d.Prefixes = strings.Split(input, ",") } + +/* +Returns true if the bucket contains specified prefix. +*/ +func (d *Destination) containsPrefix(prefix string) bool { + for _, p := range d.Prefixes { + if prefix == p { + return true + } + } + return false +} diff --git a/timespans/destinations_test.go b/timespans/destinations_test.go index c90bcab2c..d063f333b 100644 --- a/timespans/destinations_test.go +++ b/timespans/destinations_test.go @@ -7,14 +7,14 @@ import ( func TestDestinationStoreRestore(t *testing.T) { nationale = &Destination{Id: "nat", Prefixes: []string{"0257", "0256", "0723"}} s := nationale.store() - d1 := &Destination{Id: "nationale"} + d1 := &Destination{Id: "nat"} d1.restore(s) if d1.store() != s { t.Errorf("Expected %q was %q", s, d1.store()) } } -func TestKyotoStore(t *testing.T) { +func TestDestinationKyotoStore(t *testing.T) { getter, _ := NewKyotoStorage("test.kch") defer getter.Close() nationale = &Destination{Id: "nat", Prefixes: []string{"0257", "0256", "0723"}} @@ -25,7 +25,7 @@ func TestKyotoStore(t *testing.T) { } } -func TestRedisStore(t *testing.T) { +func TestDestinationRedisStore(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() nationale = &Destination{Id: "nat", Prefixes: []string{"0257", "0256", "0723"}} @@ -36,7 +36,7 @@ func TestRedisStore(t *testing.T) { } } -func TestMongoStore(t *testing.T) { +func TestDestinationMongoStore(t *testing.T) { getter, _ := NewMongoStorage("127.0.0.1", "test") defer getter.Close() nationale = &Destination{Id: "nat", Prefixes: []string{"0257", "0256", "0723"}} @@ -47,6 +47,16 @@ func TestMongoStore(t *testing.T) { } } +func TestDestinationContainsPrefix(t *testing.T) { + getter, _ := NewMongoStorage("127.0.0.1", "test") + defer getter.Close() + nationale = &Destination{Id: "nat", Prefixes: []string{"0257", "0256", "0723"}} + if !nationale.containsPrefix("0256") { + t.Error("Should contain prefix: ", nationale) + } + +} + /********************************* Benchmarks **********************************/ func BenchmarkDestinationKyotoStoreRestore(b *testing.B) { diff --git a/timespans/kyoto_storage.go b/timespans/kyoto_storage.go index c15e272be..27a14cde6 100644 --- a/timespans/kyoto_storage.go +++ b/timespans/kyoto_storage.go @@ -51,3 +51,14 @@ func (ks *KyotoStorage) GetDestination(key string) (dest *Destination, err error func (ks *KyotoStorage) SetDestination(dest *Destination) { ks.db.Set(dest.Id, dest.store()) } + +func (ks *KyotoStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) { + values, err := ks.db.Get(key) + tp = &TariffPlan{Id: key} + tp.restore(values) + return +} + +func (ks *KyotoStorage) SetTariffPlan(tp *TariffPlan) { + ks.db.Set(tp.Id, tp.store()) +} diff --git a/timespans/minute_buckets.go b/timespans/minute_buckets.go index 05de97398..d13f0278c 100644 --- a/timespans/minute_buckets.go +++ b/timespans/minute_buckets.go @@ -4,17 +4,13 @@ type MinuteBucket struct { seconds int priority int price float64 + destinationId string destination *Destination } -/* -Returns true if the bucket contains specified prefix. -*/ -func (mb *MinuteBucket) containsPrefix(prefix string) bool { - for _, p := range mb.destination.Prefixes { - if prefix == p { - return true - } +func (mb *MinuteBucket) getDestination(storage StorageGetter) (dest *Destination) { + if mb.destination == nil { + mb.destination,_ = storage.GetDestination(mb.destinationId) } - return false + return mb.destination } diff --git a/timespans/minute_buckets_test.go b/timespans/minute_buckets_test.go index e69de29bb..d8ea8c7f2 100644 --- a/timespans/minute_buckets_test.go +++ b/timespans/minute_buckets_test.go @@ -0,0 +1,15 @@ +package timespans + +import ( + "testing" +) + +func TestGetDestination(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + mb := &MinuteBucket{destinationId: "nationale"} + d := mb.getDestination(getter) + if d.Id != "nationale" || len(d.Prefixes) != 4 { + t.Error("Got wrong destination: ", d) + } +} diff --git a/timespans/mongo_storage.go b/timespans/mongo_storage.go index 4206c54ac..b0dca402c 100644 --- a/timespans/mongo_storage.go +++ b/timespans/mongo_storage.go @@ -57,3 +57,15 @@ func (ms *MongoStorage) SetDestination(dest *Destination) { ndb := ms.db.C("dest") ndb.Insert(&dest) } + +func (ms *MongoStorage) GetTariffPlan(key string) (result *TariffPlan, err error) { + ndb := ms.db.C("tp") + result = &TariffPlan{} + err = ndb.Find(bson.M{"id": key}).One(result) + return +} + +func (ms *MongoStorage) SetTariffPlan(tp *TariffPlan) { + ndb := ms.db.C("tp") + ndb.Insert(&tp) +} diff --git a/timespans/redis_storage.go b/timespans/redis_storage.go index d1d7ce5e2..673754642 100644 --- a/timespans/redis_storage.go +++ b/timespans/redis_storage.go @@ -56,3 +56,16 @@ func (rs *RedisStorage) SetDestination(dest *Destination) { rs.db.Select(rs.dbNb + 1) rs.db.Set(dest.Id, dest.store()) } + +func (rs *RedisStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) { + rs.db.Select(rs.dbNb + 2) + values, err := rs.db.Get(key) + tp = &TariffPlan{Id: key} + tp.restore(values.String()) + return +} + +func (rs *RedisStorage) SetTariffPlan(tp *TariffPlan) { + rs.db.Select(rs.dbNb + 2) + rs.db.Set(tp.Id, tp.store()) +} diff --git a/timespans/storage_interface.go b/timespans/storage_interface.go index 35bed1a77..6463ec21e 100644 --- a/timespans/storage_interface.go +++ b/timespans/storage_interface.go @@ -5,8 +5,10 @@ Interface for storage providers. */ type StorageGetter interface { Close() - GetActivationPeriods(key string) ([]*ActivationPeriod, error) - SetActivationPeriods(key string, aps []*ActivationPeriod) - GetDestination(key string) (*Destination, error) - SetDestination(dest *Destination) + GetActivationPeriods(string) ([]*ActivationPeriod, error) + SetActivationPeriods(string, []*ActivationPeriod) + GetDestination(string) (*Destination, error) + SetDestination(*Destination) + GetTariffPlan(string) (*TariffPlan, error) + SetTariffPlan(*TariffPlan) } diff --git a/timespans/tariff_plans.go b/timespans/tariff_plans.go index 086e05985..ebadf9f64 100644 --- a/timespans/tariff_plans.go +++ b/timespans/tariff_plans.go @@ -1,6 +1,50 @@ package timespans +import ( + "strconv" + "strings" +) + +/* +Structure describing a tariff plan's number of bonus items. It is uset to restore +these numbers to the user budget every month. +*/ type TariffPlan struct { Id string + SmsCredit int MinuteBuckets []*MinuteBucket } + +/* +Serializes the activation periods for the storage. Used for key-value storages. +*/ +func (tp *TariffPlan) store() (result string) { + result += strconv.Itoa(tp.SmsCredit) + ";" + for _, mb := range tp.MinuteBuckets { + var mbs string + mbs += strconv.Itoa(int(mb.seconds)) + "|" + mbs += strconv.Itoa(int(mb.priority)) + "|" + mbs += strconv.FormatFloat(mb.price, 'f', -1, 64) + "|" + mbs += mb.destinationId + result += mbs + ";" + } + return +} + +/* +De-serializes the activation periods for the storage. Used for key-value storages. +*/ +func (tp *TariffPlan) restore(input string) { + elements := strings.Split(input, ";") + tp.SmsCredit,_ = strconv.Atoi(elements[0]) + for _, mbs := range elements[1 : len(elements)-1] { + mb := &MinuteBucket{} + mbse := strings.Split(mbs, "|") + mb.seconds,_ = strconv.Atoi(mbse[0]) + mb.priority,_ = strconv.Atoi(mbse[1]) + mb.price,_ = strconv.ParseFloat(mbse[2], 64) + mb.destinationId = mbse[3] + + tp.MinuteBuckets = append(tp.MinuteBuckets, mb) + } +} diff --git a/timespans/tariff_plans_test.go b/timespans/tariff_plans_test.go index e69de29bb..6ca410fa1 100644 --- a/timespans/tariff_plans_test.go +++ b/timespans/tariff_plans_test.go @@ -0,0 +1,95 @@ +package timespans + +import ( + "testing" +) + +func TestTariffPlanStoreRestore(t *testing.T) { + b1 := &MinuteBucket{seconds: 10, priority: 10, price: 0.01, destinationId: "nationale"} + b2 := &MinuteBucket{seconds: 100, priority: 20, price: 0.0, destinationId: "retea"} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + + s := seara.store() + tp1 := &TariffPlan{Id: "seara"} + tp1.restore(s) + if tp1.store() != s { + t.Errorf("Expected %q was %q", s, tp1.store()) + } +} + +func TestTariffPlanKyotoStore(t *testing.T) { + getter, _ := NewKyotoStorage("test.kch") + defer getter.Close() + b1 := &MinuteBucket{seconds: 10, priority: 10, price: 0.01, destinationId: "nationale"} + b2 := &MinuteBucket{seconds: 100, priority: 20, price: 0.0, destinationId: "retea"} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + getter.SetTariffPlan(seara) + result, _ := getter.GetTariffPlan(seara.Id) + if result.SmsCredit != seara.SmsCredit || len(result.MinuteBuckets) != len(seara.MinuteBuckets) { + t.Errorf("Expected %q was %q", seara, result) + } +} + +func TestTariffPlanRedisStore(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{seconds: 10, priority: 10, price: 0.01, destinationId: "nationale"} + b2 := &MinuteBucket{seconds: 100, priority: 20, price: 0.0, destinationId: "retea"} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + getter.SetTariffPlan(seara) + result, _ := getter.GetTariffPlan(seara.Id) + if result.SmsCredit != seara.SmsCredit || len(result.MinuteBuckets) != len(seara.MinuteBuckets) { + t.Errorf("Expected %q was %q", seara, result) + } +} + +func TestTariffPlanMongoStore(t *testing.T) { + getter, _ := NewMongoStorage("127.0.0.1", "test") + defer getter.Close() + b1 := &MinuteBucket{seconds: 10, priority: 10, price: 0.01, destinationId: "nationale"} + b2 := &MinuteBucket{seconds: 100, priority: 20, price: 0.0, destinationId: "retea"} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + getter.SetTariffPlan(seara) + result, _ := getter.GetTariffPlan(seara.Id) + if result.SmsCredit != seara.SmsCredit || len(result.MinuteBuckets) != len(seara.MinuteBuckets) { + t.Errorf("Expected %q was %q", seara, result) + } +} + +/********************************* Benchmarks **********************************/ + +func BenchmarkTariffPlanKyotoStoreRestore(b *testing.B) { + getter, _ := NewKyotoStorage("test.kch") + defer getter.Close() + b1 := &MinuteBucket{seconds: 10, priority: 10, price: 0.01, destinationId: "nationale"} + b2 := &MinuteBucket{seconds: 100, priority: 20, price: 0.0, destinationId: "retea"} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + for i := 0; i < b.N; i++ { + getter.SetTariffPlan(seara) + getter.GetTariffPlan(seara.Id) + } +} + +func BenchmarkTariffPlanRedisStoreRestore(b *testing.B) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{seconds: 10, priority: 10, price: 0.01, destinationId: "nationale"} + b2 := &MinuteBucket{seconds: 100, priority: 20, price: 0.0, destinationId: "retea"} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + for i := 0; i < b.N; i++ { + getter.SetTariffPlan(seara) + getter.GetTariffPlan(seara.Id) + } +} + +func BenchmarkTariffPlanMongoStoreRestore(b *testing.B) { + getter, _ := NewMongoStorage("127.0.0.1", "test") + defer getter.Close() + b1 := &MinuteBucket{seconds: 10, priority: 10, price: 0.01, destinationId: "nationale"} + b2 := &MinuteBucket{seconds: 100, priority: 20, price: 0.0, destinationId: "retea"} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + for i := 0; i < b.N; i++ { + getter.SetTariffPlan(seara) + getter.GetTariffPlan(seara.Id) + } +} diff --git a/timespans/test.kch b/timespans/test.kch index 47d449be9..f17fec49c 100644 Binary files a/timespans/test.kch and b/timespans/test.kch differ diff --git a/timespans/userbudget.go b/timespans/userbudget.go index 8718cb0aa..59e7da42c 100644 --- a/timespans/userbudget.go +++ b/timespans/userbudget.go @@ -20,7 +20,7 @@ type UserBudget struct { /* Returns user's avaliable minutes for the specified destination */ -func (ub *UserBudget) GetSecondsForPrefix(prefix string) (seconds int) { +func (ub *UserBudget) GetSecondsForPrefix(storage StorageGetter, prefix string) (seconds int) { if len(ub.minuteBuckets) == 0 { log.Print("There are no minute buckets to check for user", ub.id) return @@ -28,7 +28,8 @@ func (ub *UserBudget) GetSecondsForPrefix(prefix string) (seconds int) { bestBucket := ub.minuteBuckets[0] for _, mb := range ub.minuteBuckets { - if mb.containsPrefix(prefix) && mb.priority > bestBucket.priority { + d := mb.getDestination(storage) + if d.containsPrefix(prefix) && mb.priority > bestBucket.priority { bestBucket = mb } } diff --git a/timespans/userbudget_test.go b/timespans/userbudget_test.go index 93b19540c..cd8e3827b 100644 --- a/timespans/userbudget_test.go +++ b/timespans/userbudget_test.go @@ -15,7 +15,7 @@ func TestGetSeconds(t *testing.T) { tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} ub1 := &UserBudget{id: "rif", minuteBuckets: []*MinuteBucket{b1, b2}, credit: 200, tariffPlan: tf1, resetDayOfTheMonth: 10} - seconds := ub1.GetSecondsForPrefix("0723") + seconds := ub1.GetSecondsForPrefix(nil, "0723") expected := 100 if seconds != expected { t.Errorf("Expected %v was %v", expected, seconds) @@ -28,7 +28,7 @@ func TestGetPricedSeconds(t *testing.T) { tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} ub1 := &UserBudget{id: "rif", minuteBuckets: []*MinuteBucket{b1, b2}, credit: 21, tariffPlan: tf1, resetDayOfTheMonth: 10} - seconds := ub1.GetSecondsForPrefix("0723") + seconds := ub1.GetSecondsForPrefix(nil, "0723") expected := 21 if seconds != expected { t.Errorf("Expected %v was %v", expected, seconds) @@ -46,6 +46,6 @@ func BenchmarkGetSecondForPrefix(b *testing.B) { ub1 := &UserBudget{id: "rif", minuteBuckets: []*MinuteBucket{b1, b2}, credit: 21, tariffPlan: tf1, resetDayOfTheMonth: 10} b.StartTimer() for i := 0; i < b.N; i++ { - ub1.GetSecondsForPrefix("0723") + ub1.GetSecondsForPrefix(nil,"0723") } }