From bb2916c42c0442594275ecfa603cb8e1f8add336 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 4 Jul 2012 19:16:08 +0300 Subject: [PATCH] MyMarshaler fully impelmented --- timespans/action_timing.go | 7 ++- timespans/action_trigger.go | 29 ++++++++++ timespans/actions_test.go | 21 ++++++- timespans/activationperiod.go | 1 + timespans/activationperiod_test.go | 37 ++++++++++--- timespans/storage_interface.go | 2 + timespans/units_counter.go | 82 +++++++++++++++++++++++++++ timespans/units_counter_test.go | 43 +++++++++++++++ timespans/userbalance.go | 89 +++++++++++++++++++++++------- timespans/userbalance_test.go | 35 ++++++++++++ 10 files changed, 312 insertions(+), 34 deletions(-) create mode 100644 timespans/units_counter.go create mode 100644 timespans/units_counter_test.go diff --git a/timespans/action_timing.go b/timespans/action_timing.go index 75c69a2aa..0fd7062ed 100644 --- a/timespans/action_timing.go +++ b/timespans/action_timing.go @@ -204,9 +204,9 @@ Serializes the action timing for the storage. Used for key-value storages. func (at *ActionTiming) store() (result string) { result += at.Tag + "|" for _, ubi := range at.UserBalanceIds { - result += ubi + ";" + result += ubi + "," } - result = strings.TrimRight(result, ";") + "|" + result = strings.TrimRight(result, ",") + "|" result += at.Timing.store() + "|" result += strconv.FormatFloat(at.Weight, 'f', -1, 64) + "|" result += at.ActionsId @@ -219,9 +219,10 @@ De-serializes the action timing for the storage. Used for key-value storages. func (at *ActionTiming) restore(input string) { elements := strings.Split(input, "|") at.Tag = elements[0] - for _, ubi := range strings.Split(elements[1], ";") { + for _, ubi := range strings.Split(elements[1], ",") { at.UserBalanceIds = append(at.UserBalanceIds, ubi) } + at.Timing = &Interval{} at.Timing.restore(elements[2]) at.Weight, _ = strconv.ParseFloat(elements[3], 64) diff --git a/timespans/action_trigger.go b/timespans/action_trigger.go index ee7bcff00..72a03a1a4 100644 --- a/timespans/action_trigger.go +++ b/timespans/action_trigger.go @@ -21,6 +21,8 @@ package timespans import ( "log" "sort" + "strconv" + "strings" ) type ActionTrigger struct { @@ -73,3 +75,30 @@ func (atpl ActionTriggerPriotityList) Less(i, j int) bool { func (atpl ActionTriggerPriotityList) Sort() { sort.Sort(atpl) } + +/* +Serializes the action trigger for the storage. Used for key-value storages. +*/ +func (at *ActionTrigger) store() (result string) { + result += at.BalanceId + ";" + result += at.DestinationId + ";" + result += at.ActionsId + ";" + result += strconv.FormatFloat(at.ThresholdValue, 'f', -1, 64) + ";" + result += strconv.FormatFloat(at.Weight, 'f', -1, 64) + return +} + +/* +De-serializes the action timing for the storage. Used for key-value storages. +*/ +func (at *ActionTrigger) restore(input string) { + elements := strings.Split(input, ";") + if len(elements) != 5 { + return + } + at.BalanceId = elements[0] + at.DestinationId = elements[1] + at.ActionsId = elements[2] + at.ThresholdValue, _ = strconv.ParseFloat(elements[3], 64) + at.Weight, _ = strconv.ParseFloat(elements[4], 64) +} diff --git a/timespans/actions_test.go b/timespans/actions_test.go index e513d9e37..4ebec5e23 100644 --- a/timespans/actions_test.go +++ b/timespans/actions_test.go @@ -44,7 +44,7 @@ func TestActionTimingStoreRestore(t *testing.T) { ActionsId: "Commando", } r := at.store() - if string(r) != "test|one;two;three|1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;1|10|Commando" { + if string(r) != "test|one,two,three|1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;1|10|Commando" { t.Errorf("Error serializing action timing: %v", string(r)) } o := &ActionTiming{} @@ -54,6 +54,25 @@ func TestActionTimingStoreRestore(t *testing.T) { } } +func TestActionTriggerStoreRestore(t *testing.T) { + at := &ActionTrigger{ + BalanceId: CREDIT, + ThresholdValue: 100.0, + DestinationId: "NAT", + Weight: 10.0, + ActionsId: "Commando", + } + r := at.store() + if string(r) != "MONETARY;NAT;Commando;100;10" { + t.Errorf("Error serializing action trigger: %v", string(r)) + } + o := &ActionTrigger{} + o.restore(r) + if !reflect.DeepEqual(o, at) { + t.Errorf("Expected %v was %v", at, o) + } +} + func TestActionTimingNothing(t *testing.T) { at := &ActionTiming{} st := at.GetNextStartTime() diff --git a/timespans/activationperiod.go b/timespans/activationperiod.go index 1865becdd..be58db0c8 100644 --- a/timespans/activationperiod.go +++ b/timespans/activationperiod.go @@ -69,6 +69,7 @@ func (ap *ActivationPeriod) store() (result string) { for _, i := range ap.Intervals { result += i.store() + "|" } + result = strings.TrimRight(result, "|") return } diff --git a/timespans/activationperiod_test.go b/timespans/activationperiod_test.go index 40bd60284..acd0e44b6 100644 --- a/timespans/activationperiod_test.go +++ b/timespans/activationperiod_test.go @@ -42,7 +42,7 @@ func TestApStoreRestore(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result := ap.store() - expected := "1328106601000000000|2;1;3,4;14:30:00;15:00:00;0;0;0;0|" + expected := "1328106601000000000|2;1;3,4;14:30:00;15:00:00;0;0;0;0" if result != expected { t.Errorf("Expected %q was %q", expected, result) } @@ -166,14 +166,7 @@ func TestApAddIntervalIfNotPresent(t *testing.T) { /**************************** Benchmarks *************************************/ -func BenchmarkActivationPeriodRestore(b *testing.B) { - ap := ActivationPeriod{} - for i := 0; i < b.N; i++ { - json.Unmarshal([]byte("1328106601;2|1|3,4|14:30:00|15:00:00|0|0|0|0;"), &ap) - } -} - -func BenchmarkActivationPeriodStoreRestore(b *testing.B) { +func BenchmarkActivationPeriodStoreRestoreJson(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &Interval{Months: []time.Month{time.February}, @@ -191,3 +184,29 @@ func BenchmarkActivationPeriodStoreRestore(b *testing.B) { json.Unmarshal(result, &ap1) } } + +func BenchmarkActivationPeriodRestore(b *testing.B) { + ap := ActivationPeriod{} + for i := 0; i < b.N; i++ { + ap.restore("1328106601000000000|2;1;3,4;14:30:00;15:00:00;0;0;0;0") + } +} + +func BenchmarkActivationPeriodStoreRestore(b *testing.B) { + b.StopTimer() + d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) + i := &Interval{Months: []time.Month{time.February}, + MonthDays: []int{1}, + WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, + StartTime: "14:30:00", + EndTime: "15:00:00"} + ap := &ActivationPeriod{ActivationTime: d} + ap.AddInterval(i) + + ap1 := ActivationPeriod{} + b.StartTimer() + for i := 0; i < b.N; i++ { + result := ap.store() + ap1.restore(result) + } +} diff --git a/timespans/storage_interface.go b/timespans/storage_interface.go index 577417b32..70e7058c1 100644 --- a/timespans/storage_interface.go +++ b/timespans/storage_interface.go @@ -23,6 +23,7 @@ import ( "encoding/gob" "encoding/json" "errors" + "log" "strings" ) @@ -194,6 +195,7 @@ func (mm *MyMarshaler) Unmarshal(data []byte, v interface{}) (err error) { return nil } + log.Print("Using default gob marshalling!") mm.buf.Reset() mm.buf.Write(data) return gob.NewDecoder(&mm.buf).Decode(v) diff --git a/timespans/units_counter.go b/timespans/units_counter.go new file mode 100644 index 000000000..27f165f27 --- /dev/null +++ b/timespans/units_counter.go @@ -0,0 +1,82 @@ +/* +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 +*/ + +package timespans + +import ( + "strconv" + "strings" +) + +// Amount of a trafic of a certain type +type UnitsCounter struct { + Direction string + BalanceId string + Units float64 + Weight float64 + MinuteBuckets []*MinuteBucket +} + +// Structure to store actions according to weight +type countersorter []*UnitsCounter + +func (s countersorter) Len() int { + return len(s) +} + +func (s countersorter) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s countersorter) Less(i, j int) bool { + return s[i].Weight < s[j].Weight +} + +/* +Serializes the unit counter for the storage. Used for key-value storages. +*/ +func (uc *UnitsCounter) store() (result string) { + result += uc.Direction + "/" + result += uc.BalanceId + "/" + result += strconv.FormatFloat(uc.Units, 'f', -1, 64) + "/" + result += strconv.FormatFloat(uc.Weight, 'f', -1, 64) + "/" + for _, mb := range uc.MinuteBuckets { + result += mb.store() + "," + } + result = strings.TrimRight(result, ",") + return +} + +/* +De-serializes the unit counter for the storage. Used for key-value storages. +*/ +func (uc *UnitsCounter) restore(input string) { + elements := strings.Split(input, "/") + if len(elements) != 5 { + return + } + uc.Direction = elements[0] + uc.BalanceId = elements[1] + uc.Units, _ = strconv.ParseFloat(elements[2], 64) + uc.Weight, _ = strconv.ParseFloat(elements[3], 64) + for _, mbs := range strings.Split(elements[4], ",") { + mb := &MinuteBucket{} + mb.restore(mbs) + uc.MinuteBuckets = append(uc.MinuteBuckets, mb) + } +} diff --git a/timespans/units_counter_test.go b/timespans/units_counter_test.go new file mode 100644 index 000000000..23fcc8c80 --- /dev/null +++ b/timespans/units_counter_test.go @@ -0,0 +1,43 @@ +/* +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 +*/ + +package timespans + +import ( + "reflect" + "testing" +) + +func TestUnitsCounterStoreRestore(t *testing.T) { + uc := &UnitsCounter{ + Direction: OUTBOUND, + BalanceId: SMS, + Units: 100, + Weight: 10, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, + } + r := uc.store() + if string(r) != "OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET" { + t.Errorf("Error serializing units counter: %v", string(r)) + } + o := &UnitsCounter{} + o.restore(r) + if !reflect.DeepEqual(o, uc) { + t.Errorf("Expected %v was %v", uc, o) + } +} diff --git a/timespans/userbalance.go b/timespans/userbalance.go index 2b9a21592..327c77a9f 100644 --- a/timespans/userbalance.go +++ b/timespans/userbalance.go @@ -19,7 +19,8 @@ along with this program. If not, see package timespans import ( - // "log" + "strconv" + "strings" "sync" ) @@ -291,26 +292,72 @@ Resets the user balance items to their tariff plan values. return } */ -// Amount of a trafic of a certain type -type UnitsCounter struct { - Direction string - BalanceId string - Units float64 - Weight float64 - MinuteBuckets []*MinuteBucket + +/* +Serializes the user balance for the storage. Used for key-value storages. +*/ +func (ub *UserBalance) store() (result string) { + result += ub.Id + "|" + result += ub.Type + "|" + for k, v := range ub.BalanceMap { + result += k + ":" + strconv.FormatFloat(v, 'f', -1, 64) + "#" + } + result = strings.TrimRight(result, "#") + "|" + for _, mb := range ub.MinuteBuckets { + result += mb.store() + "#" + } + result = strings.TrimRight(result, "#") + "|" + for _, uc := range ub.UnitCounters { + result += uc.store() + "#" + } + result = strings.TrimRight(result, "#") + "|" + for _, at := range ub.ActionTriggers { + result += at.store() + "#" + } + result = strings.TrimRight(result, "#") + return } -// Structure to store actions according to weight -type countersorter []*UnitsCounter - -func (s countersorter) Len() int { - return len(s) -} - -func (s countersorter) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -func (s countersorter) Less(i, j int) bool { - return s[i].Weight < s[j].Weight +/* +De-serializes the user balance for the storage. Used for key-value storages. +*/ +func (ub *UserBalance) restore(input string) { + elements := strings.Split(input, "|") + ub.Id = elements[0] + ub.Type = elements[1] + if ub.BalanceMap == nil { + ub.BalanceMap = make(map[string]float64, 0) + } + for _, maps := range strings.Split(elements[2], "#") { + kv := strings.Split(maps, ":") + if len(kv) != 2 { + continue + } + value, _ := strconv.ParseFloat(kv[1], 64) + ub.BalanceMap[kv[0]] = value + } + for _, mbs := range strings.Split(elements[3], "#") { + if mbs == "" { + continue + } + mb := &MinuteBucket{} + mb.restore(mbs) + ub.MinuteBuckets = append(ub.MinuteBuckets, mb) + } + for _, ucs := range strings.Split(elements[4], "#") { + if ucs == "" { + continue + } + uc := &UnitsCounter{} + uc.restore(ucs) + ub.UnitCounters = append(ub.UnitCounters, uc) + } + for _, ats := range strings.Split(elements[5], "#") { + if ats == "" { + continue + } + at := &ActionTrigger{} + at.restore(ats) + ub.ActionTriggers = append(ub.ActionTriggers, at) + } } diff --git a/timespans/userbalance_test.go b/timespans/userbalance_test.go index e18fda960..da84eea7c 100644 --- a/timespans/userbalance_test.go +++ b/timespans/userbalance_test.go @@ -35,6 +35,41 @@ func init() { } func TestUserBalanceStoreRestore(t *testing.T) { + uc := &UnitsCounter{ + Direction: OUTBOUND, + BalanceId: SMS, + Units: 100, + Weight: 10, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, + } + at := &ActionTrigger{ + BalanceId: CREDIT, + ThresholdValue: 100.0, + DestinationId: "NAT", + Weight: 10.0, + ActionsId: "Commando", + } + ub := &UserBalance{ + Id: "rif", + Type: UB_TYPE_POSTPAID, + BalanceMap: map[string]float64{SMS: 14, TRAFFIC: 1024}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, + UnitCounters: []*UnitsCounter{uc, uc}, + ActionTriggers: ActionTriggerPriotityList{at, at, at}, + } + r := ub.store() + if string(r) != "rif|postpaid|SMS:14#INTERNET:1024|0;20;1;0;NAT#0;10;10;0;RET|OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET#OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET|MONETARY;NAT;Commando;100;10#MONETARY;NAT;Commando;100;10#MONETARY;NAT;Commando;100;10" && + string(r) != "rif|postpaid|INTERNET:1024#SMS:14|0;20;1;0;NAT#0;10;10;0;RET|OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET#OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET|MONETARY;NAT;Commando;100;10#MONETARY;NAT;Commando;100;10#MONETARY;NAT;Commando;100;10" { + t.Errorf("Error serializing action timing: %v", string(r)) + } + o := &UserBalance{} + o.restore(r) + if !reflect.DeepEqual(o, ub) { + t.Errorf("Expected %v was %v", ub, o) + } +} + +func TestUserBalanceStorageStoreRestore(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT: 21}}