diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index ee2ab2b36..40bd8e346 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -32,12 +32,6 @@ type AttrAcntAction struct { Direction string } -// Returns the balance id as used internally -// eg: *out:cgrates.org:1005 -func BalanceId(tenant, account, direction string) string { - return fmt.Sprintf("%s:%s:%s", direction, tenant, account) -} - type AccountActionTiming struct { Id string // The id to reference this particular ActionTiming ActionTimingsId string // The id of the ActionTimings profile attached to the account @@ -56,7 +50,7 @@ func (self *ApierV1) GetAccountActionTimings(attrs AttrAcntAction, reply *[]*Acc } for _, ats := range allATs { for _, at := range ats { - if utils.IsSliceMember(at.UserBalanceIds, BalanceId(attrs.Tenant, attrs.Account, attrs.Direction)) { + if utils.IsSliceMember(at.UserBalanceIds, utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)) { accountATs = append(accountATs, &AccountActionTiming{Id: at.Id, ActionTimingsId: at.Tag, ActionsId: at.ActionsId, NextExecTime: at.GetNextStartTime()}) } } @@ -84,7 +78,7 @@ func (self *ApierV1) RemActionTiming(attrs AttrRemActionTiming, reply *string) e return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) } } - _, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX+attrs.ActionTimingId, func() (float64, error) { // ToDo: Expand the scheduler to consider the locks also + _, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { ats, err := self.AccountDb.GetActionTimings(attrs.ActionTimingsId) if err != nil { return 0, err @@ -160,3 +154,73 @@ func (self *ApierV1) RemAccountActionTriggers(attrs AttrRemAcntActionTriggers, r *reply = OK return nil } + + +type AttrSetAccount struct { + Tenant string + Direction string + Account string + Type string // <*prepaid|*postpaid> + ActionTimingsId string +} + +// Ads a new account into dataDb. If already defined, returns success. +func (self *ApierV1) SetAccount(attr AttrSetAccount, reply *string) error { + if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Direction", "Account"}); len(missing) != 0 { + return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) + } + balanceId := utils.BalanceKey(attr.Tenant, attr.Account, attr.Direction) + var ub *engine.UserBalance + var ats engine.ActionTimings + _, err := engine.AccLock.Guard(balanceId, func() (float64, error) { + if bal, _ := self.AccountDb.GetUserBalance(balanceId); bal != nil { + ub = bal + } else { // Not found in db, create it here + if len(attr.Type) == 0 { + attr.Type = engine.UB_TYPE_PREPAID + } else if !utils.IsSliceMember([]string{engine.UB_TYPE_POSTPAID, engine.UB_TYPE_PREPAID}, attr.Type) { + return 0, fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, "Type") + } + ub = &engine.UserBalance{ + Id: balanceId, + Type: attr.Type, + } + } + + if len(attr.ActionTimingsId) != 0 { + var err error + ats, err = self.AccountDb.GetActionTimings(attr.ActionTimingsId) + if err != nil { + return 0, err + } + for _, at := range ats { + at.UserBalanceIds = append(at.UserBalanceIds, balanceId) + } + } + // All prepared, save account + if err := self.AccountDb.SetUserBalance(ub); err != nil { + return 0, err + } + return 0, nil + }) + if err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + if len(ats) != 0 { + _, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { // ToDo: Try locking it above on read somehow + if err := self.AccountDb.SetActionTimings(attr.ActionTimingsId, ats); err != nil { + return 0, err + } + return 0, nil + }) + if err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + if self.Sched != nil { + self.Sched.LoadActionTimings(self.AccountDb) + self.Sched.Restart() + } + } + *reply = OK // This will mark saving of the account, error still can show up in actionTimingsId + return nil +} diff --git a/apier/v1/apier.go b/apier/v1/apier.go index 8496e7c2e..56335ec77 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -94,11 +94,11 @@ type AttrAddBalance struct { BalanceId string Direction string Value float64 + Overwrite bool // When true it will reset if the balance is already there } func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) - if _, err := self.AccountDb.GetUserBalance(tag); err != nil { // create user balance if not exists ub := &engine.UserBalance{ @@ -109,7 +109,6 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { return err } } - at := &engine.ActionTiming{ UserBalanceIds: []string{tag}, } @@ -117,9 +116,11 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { if attr.Direction == "" { attr.Direction = engine.OUTBOUND } - - at.SetActions(engine.Actions{&engine.Action{ActionType: engine.TOPUP, BalanceId: attr.BalanceId, Direction: attr.Direction, Balance: &engine.Balance{Value: attr.Value}}}) - + aType := engine.TOPUP + if attr.Overwrite { + aType = engine.TOPUP_RESET + } + at.SetActions(engine.Actions{&engine.Action{ActionType: aType, BalanceId: attr.BalanceId, Direction: attr.Direction, Balance: &engine.Balance{Value: attr.Value}}}) if err := at.Execute(); err != nil { *reply = err.Error() return err @@ -403,49 +404,6 @@ func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string return nil } -type AttrAddAccount struct { - Tenant string - Direction string - Account string - Type string // prepaid-postpaid - ActionTimingsId string -} - -// Ads a new account into dataDb. If already defined, returns success. -func (self *ApierV1) AddAccount(attr AttrAddAccount, reply *string) error { - if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Direction", "Account", "Type", "ActionTimingsId"}); len(missing) != 0 { - return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) - } - tag := utils.BalanceKey(attr.Tenant, attr.Account, attr.Direction) - ub := &engine.UserBalance{ - Id: tag, - Type: attr.Type, - } - - if attr.ActionTimingsId != "" { - if ats, err := self.AccountDb.GetActionTimings(attr.ActionTimingsId); err == nil { - for _, at := range ats { - engine.Logger.Debug(fmt.Sprintf("Found action timings: %v", at)) - at.UserBalanceIds = append(at.UserBalanceIds, tag) - } - err = self.AccountDb.SetActionTimings(attr.ActionTimingsId, ats) - if err != nil { - if self.Sched != nil { - self.Sched.LoadActionTimings(self.AccountDb) - self.Sched.Restart() - } - } - if err := self.AccountDb.SetUserBalance(ub); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - } else { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - } - *reply = OK - return nil -} - // Process dependencies and load a specific AccountActions profile from storDb into dataDb. func (self *ApierV1) LoadAccountActions(attrs utils.TPAccountActions, reply *string) error { if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "Account", "Direction"}); len(missing) != 0 { diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go index 0c3ff8b51..bc9a86f51 100644 --- a/apier/v1/apier_local_test.go +++ b/apier/v1/apier_local_test.go @@ -909,6 +909,32 @@ func TestApierAddBalance(t *testing.T) { } else if reply != "OK" { t.Errorf("Calling ApierV1.AddBalance received: %s", reply) } + attrs = &AttrAddBalance{Tenant: "cgrates.org", Account: "dan3", BalanceId: "*monetary", Direction: "*out", Value: 1.5} + if err := rater.Call("ApierV1.AddBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.AddBalance: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.AddBalance received: %s", reply) + } + attrs = &AttrAddBalance{Tenant: "cgrates.org", Account: "dan3", BalanceId: "*monetary", Direction: "*out", Value: 2.1} + if err := rater.Call("ApierV1.AddBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.AddBalance: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.AddBalance received: %s", reply) + } + attrs = &AttrAddBalance{Tenant: "cgrates.org", Account: "dan6", BalanceId: "*monetary", Direction: "*out", Value: 2.1} + if err := rater.Call("ApierV1.AddBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.AddBalance: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.AddBalance received: %s", reply) + } + attrs = &AttrAddBalance{Tenant: "cgrates.org", Account: "dan6", BalanceId: "*monetary", Direction: "*out", Value: 1, Overwrite: true} + if err := rater.Call("ApierV1.AddBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.AddBalance: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.AddBalance received: %s", reply) + } + + } // Test here ExecuteAction @@ -985,7 +1011,7 @@ func TestApierAddTriggeredAction(t *testing.T) { reply2 := "" attrs2 := new(AttrAddActionTrigger) *attrs2 = *attrs - attrs2.Account = "dan3" // Does not exist so it should error when adding triggers on it + attrs2.Account = "dan10" // Does not exist so it should error when adding triggers on it // Add trigger to an account which does n exist if err := rater.Call("ApierV1.AddTriggeredAction", attrs2, &reply2); err == nil || reply2 == "OK" { t.Error("Expecting error on ApierV1.AddTriggeredAction.", err, reply2) @@ -1036,35 +1062,36 @@ func TestApierRemAccountActionTriggers(t *testing.T) { } -// Test here AddAccount -func TestApierAddAccount(t *testing.T) { - if !*testLocal { - return - } - reply := "" - attrs := &AttrAddAccount{Tenant: "cgrates.org", Direction: "*out", Account: "dan4", Type: "prepaid", ActionTimingsId: "ATMS_1"} - if err := rater.Call("ApierV1.AddAccount", attrs, &reply); err != nil { - t.Error("Got error on ApierV1.AddAccount: ", err.Error()) - } else if reply != "OK" { - t.Errorf("Calling ApierV1.AddAccount received: %s", reply) - } - reply2 := "" - attrs2 := new(AttrAddAccount) - *attrs2 = *attrs - attrs2.ActionTimingsId = "DUMMY_DATA" // Does not exist so it should error when adding triggers on it - // Add account with actions timing which does not exist - if err := rater.Call("ApierV1.AddAccount", attrs2, &reply2); err == nil || reply2 == "OK" { - t.Error("Expecting error on ApierV1.AddAccount.", err, reply2) - } +// Test here SetAccount +func TestApierSetAccount(t *testing.T) { + if !*testLocal { + return + } + reply := "" + attrs := &AttrSetAccount{Tenant: "cgrates.org", Direction: "*out", Account: "dan7", Type: "*prepaid", ActionTimingsId: "ATMS_1"} + if err := rater.Call("ApierV1.SetAccount", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.SetAccount: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.SetAccount received: %s", reply) + } + reply2 := "" + attrs2 := new(AttrSetAccount) + *attrs2 = *attrs + attrs2.ActionTimingsId = "DUMMY_DATA" // Does not exist so it should error when adding triggers on it + // Add account with actions timing which does not exist + if err := rater.Call("ApierV1.SetAccount", attrs2, &reply2); err == nil || reply2 == "OK" { // OK is not welcomed + t.Error("Expecting error on ApierV1.SetAccount.", err, reply2) + } } + // Test here GetAccountActionTimings func TestApierGetAccountActionTimings(t *testing.T) { if !*testLocal { return } var reply []*AccountActionTiming - req := AttrAcntAction{Tenant: "cgrates.org", Account:"dan4", Direction: "*out"} + req := AttrAcntAction{Tenant: "cgrates.org", Account:"dan7", Direction: "*out"} if err := rater.Call("ApierV1.GetAccountActionTimings", req, &reply); err != nil { t.Error("Got error on ApierV1.GetAccountActionTimings: ", err.Error()) } else if len(reply) != 1 { @@ -1122,6 +1149,18 @@ func TestApierGetBalance(t *testing.T) { } else if reply != 10 { t.Errorf("Calling ApierV1.GetBalance expected: 10, received: %f", reply) } + attrs = &AttrGetBalance{Tenant: "cgrates.org", Account: "dan3", BalanceId: "*monetary", Direction: "*out"} + if err := rater.Call("ApierV1.GetBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.GetBalance: ", err.Error()) + } else if reply != 3.6 { + t.Errorf("Calling ApierV1.GetBalance expected: 3.6, received: %f", reply) + } + attrs = &AttrGetBalance{Tenant: "cgrates.org", Account: "dan6", BalanceId: "*monetary", Direction: "*out"} + if err := rater.Call("ApierV1.GetBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.GetBalance: ", err.Error()) + } else if reply != 1 { + t.Errorf("Calling ApierV1.GetBalance expected: 1, received: %f", reply) + } } // Test here LoadTariffPlanFromFolder diff --git a/console/add_account.go b/console/add_account.go index 85ce27d52..ade30f214 100644 --- a/console/add_account.go +++ b/console/add_account.go @@ -30,7 +30,7 @@ func init() { // Commander implementation type CmdAddAccount struct { rpcMethod string - rpcParams *apier.AttrAddAccount + rpcParams *apier.AttrSetAccount rpcResult string } @@ -41,8 +41,8 @@ func (self *CmdAddAccount) Usage(name string) string { // set param defaults func (self *CmdAddAccount) defaults() error { - self.rpcMethod = "ApierV1.AddAccount" - self.rpcParams = &apier.AttrAddAccount{Direction: "*out"} + self.rpcMethod = "ApierV1.SetAccount" + self.rpcParams = &apier.AttrSetAccount{Direction: "*out"} return nil } diff --git a/docs/apicalls.rst b/docs/apicalls.rst index 0b6b94fdd..51e77717e 100644 --- a/docs/apicalls.rst +++ b/docs/apicalls.rst @@ -326,21 +326,21 @@ AddTriggeredAction Example AddTriggeredAction(attr \*AttrAddActionTrigger, reply \*float64) -AddAcount +SetAcount +++++++++ :: - type AttrAddAccount struct { + type AttrSetAccount struct { Tenant string Direction string Account string - Type string // prepaid-postpaid + Type string // <*prepaid|*postpaid> ActionTimingsId string } Example - AddAccount(attr \*AttrAddAccount, reply \*float64) + AddAccount(attr \*AttrAddAccount, reply \*string) diff --git a/engine/accountlock.go b/engine/accountlock.go index 6ea5c104d..3a7da3d6f 100644 --- a/engine/accountlock.go +++ b/engine/accountlock.go @@ -82,6 +82,10 @@ func (cm *AccountLock) GuardMany(names []string, handler func() (float64, error) } lock <- true reply, err = handler() + } + reply, err = handler() + for _, name := range names { + lock := AccLock.queue[name] <-lock } return diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index b655a4be6..14a1a50eb 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -40,10 +40,10 @@ func NewScheduler() *Scheduler { func (s *Scheduler) Loop() { for { - s.Lock() for len(s.queue) == 0 { //hang here if empty <-s.restartLoop } + s.Lock() a0 := s.queue[0] now := time.Now() if a0.GetNextStartTime().Equal(now) || a0.GetNextStartTime().Before(now) { @@ -52,19 +52,20 @@ func (s *Scheduler) Loop() { s.queue = append(s.queue, a0) s.queue = s.queue[1:] sort.Sort(s.queue) + s.Unlock() } else { + s.Unlock() d := a0.GetNextStartTime().Sub(now) // engine.Logger.Info(fmt.Sprintf("Timer set to wait for %v", d)) s.timer = time.NewTimer(d) select { case <-s.timer.C: // timer has expired - engine.Logger.Info(fmt.Sprintf("Time for action on %v", s.queue[0])) + engine.Logger.Info(fmt.Sprintf("Time for action on %v", a0)) case <-s.restartLoop: // nothing to do, just continue the loop } } - s.Unlock() } } @@ -93,7 +94,10 @@ func (s *Scheduler) LoadActionTimings(storage engine.AccountingStorage) { } } if toBeSaved { - storage.SetActionTimings(key, newAts) + engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { + storage.SetActionTimings(key, newAts) + return 0, nil + }) } } sort.Sort(s.queue)