diff --git a/apier/apier.go b/apier/apier.go index 6915ba96e..de1fd3578 100644 --- a/apier/apier.go +++ b/apier/apier.go @@ -19,10 +19,245 @@ along with this program. If not, see package apier import ( + "errors" + "fmt" "github.com/cgrates/cgrates/rater" + "github.com/cgrates/cgrates/scheduler" + "github.com/cgrates/cgrates/utils" + "log" ) type Apier struct { StorDb rater.DataStorage Getter rater.DataStorage + Sched *scheduler.Scheduler +} + +type AttrDestination struct { + Id string + Prefixes []string +} + +func (self *Apier) GetDestination(attr *AttrDestination, reply *AttrDestination) error { + if dst, err := self.Getter.GetDestination(attr.Id); err != nil { + return errors.New(utils.ERR_NOT_FOUND) + } else { + reply.Id = dst.Id + reply.Prefixes = dst.Prefixes + } + return nil +} + +func (self *Apier) SetDestination(attr *AttrDestination, reply *float64) error { + d := &rater.Destination{ + Id: attr.Id, + Prefixes: attr.Prefixes, + } + if err := self.Getter.SetDestination(d); err != nil { + return err + } + return nil +} + +type AttrGetBalance struct { + Tenant string + Account string + BalanceId string + Direction string +} + +// Get balance +func (self *Apier) GetBalance(attr *AttrGetBalance, reply *float64) error { + tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) + userBalance, err := self.Getter.GetUserBalance(tag) + if err != nil { + return err + } + + if attr.Direction == "" { + attr.Direction = rater.OUTBOUND + } + + if balance, balExists := userBalance.BalanceMap[attr.BalanceId+attr.Direction]; !balExists { + *reply = 0.0 + } else { + *reply = balance + } + return nil +} + +type AttrAddBalance struct { + Tenant string + Account string + BalanceId string + Direction string + Value float64 +} + +func (self *Apier) AddBalance(attr *AttrAddBalance, reply *float64) error { + // what storage instance do we use? + tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) + + if _, err := self.Getter.GetUserBalance(tag); err != nil { + // create user balance if not exists + ub := &rater.UserBalance{ + Id: tag, + } + if err := self.Getter.SetUserBalance(ub); err != nil { + return err + } + } + + at := &rater.ActionTiming{ + UserBalanceIds: []string{tag}, + } + + if attr.Direction == "" { + attr.Direction = rater.OUTBOUND + } + + at.SetActions(rater.Actions{&rater.Action{ActionType: rater.TOPUP, BalanceId: attr.BalanceId, Direction: attr.Direction, Units: attr.Value}}) + + if err := at.Execute(); err != nil { + return err + } + // what to put in replay? + return nil +} + +type AttrExecuteAction struct { + Direction string + Tenant string + Account string + BalanceId string + ActionsId string +} + +func (self *Apier) ExecuteAction(attr *AttrExecuteAction, reply *float64) error { + tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) + at := &rater.ActionTiming{ + UserBalanceIds: []string{tag}, + ActionsId: attr.ActionsId, + } + + if err := at.Execute(); err != nil { + return err + } + // what to put in replay + return nil +} + +type AttrSetRatingProfile struct { + Direction string + Tenant string + TOR string + Subject string + SourceSubject string +} + +func (self *Apier) SetRatingProfile(attr *AttrSetRatingProfile, reply *float64) error { + cd := &rater.CallDescriptor{ + Direction: attr.Direction, + Tenant: attr.Tenant, + TOR: attr.TOR, + Subject: attr.Subject, + } + destination, err := self.Getter.GetRatingProfile(cd.GetKey()) + log.Print("1: " + cd.GetKey()) + if err != nil { + destination = &rater.RatingProfile{Id: cd.GetKey()} + } + cd.Subject = attr.SourceSubject + source, err := self.Getter.GetRatingProfile(cd.GetKey()) + log.Print("2: " + cd.GetKey()) + if err != nil { + log.Print("here: " + err.Error()) + return err + } + destination.DestinationMap = source.DestinationMap + destination.FallbackKey = source.FallbackKey + err = self.Getter.SetRatingProfile(destination) + return err +} + +type AttrActionTrigger struct { + Tenant string + Account string + Direction string + BalanceId string + ThresholdValue float64 + DestinationId string + Weight float64 + ActionsId string +} + +func (self *Apier) AddTriggeredAction(attr *AttrActionTrigger, reply *float64) error { + if attr.Direction == "" { + attr.Direction = rater.OUTBOUND + } + + at := &rater.ActionTrigger{ + Id: utils.GenUUID(), + BalanceId: attr.BalanceId, + Direction: attr.Direction, + ThresholdValue: attr.ThresholdValue, + DestinationId: attr.DestinationId, + Weight: attr.Weight, + ActionsId: attr.ActionsId, + Executed: false, + } + + tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) + var dbErr error + rater.AccLock.Guard(tag, func() (float64, error) { + userBalance, err := self.Getter.GetUserBalance(tag) + if err != nil { + dbErr = err + return 0, err + } + + userBalance.ActionTriggers = append(userBalance.ActionTriggers, at) + + if err = self.Getter.SetUserBalance(userBalance); err != nil { + dbErr = err + return 0, err + } + return 0, nil + }) + + return dbErr +} + +type AttrAccount struct { + Tenant string + Direction string + Account string + Type string // prepaid-postpaid + ActionTimingsId string +} + +func (self *Apier) AddAccount(attr *AttrAccount, reply *float64) error { + tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) + ub := &rater.UserBalance{ + Id: tag, + Type: attr.Type, + } + if err := self.Getter.SetUserBalance(ub); err != nil { + return err + } + if attr.ActionTimingsId != "" { + if ats, err := self.Getter.GetActionTimings(attr.ActionTimingsId); err == nil { + for _, at := range ats { + at.UserBalanceIds = append(at.UserBalanceIds, tag) + } + self.Getter.SetActionTimings(attr.ActionTimingsId, ats) + if self.Sched != nil { + self.Sched.LoadActionTimings(self.Getter) + self.Sched.Restart() + } + } else { + return err + } + } + return nil } diff --git a/apier/tpdestinations.go b/apier/tpdestinations.go index 0fb9e310d..16ef2d22f 100644 --- a/apier/tpdestinations.go +++ b/apier/tpdestinations.go @@ -21,13 +21,13 @@ package apier import ( "errors" "fmt" - "github.com/cgrates/cgrates/rater" + "github.com/cgrates/cgrates/rater" "github.com/cgrates/cgrates/utils" ) type AttrGetTPDestinations struct { TPid string - DestinationsTag string + DestinationsTag string } // Return destinations profile for a destination tag received as parameter @@ -44,184 +44,3 @@ func (self *Apier) GetTPDestinations(attrs AttrGetTPDestinations, reply *rater.D } return nil } - -type AttrDestination struct { - Id string - Prefixes []string -} - -func (self *Apier) GetDestination(attr *AttrDestination, reply *AttrDestination) error { - if dst, err := self.Getter.GetDestination(attr.Id); err != nil { - return errors.New(utils.ERR_NOT_FOUND) - } else { - reply.Id = dst.Id - reply.Prefixes = dst.Prefixes - } - return nil -} - -func (self *Apier) SetDestination(attr *AttrDestination, reply *float64) error { - d := &rater.Destination{ - Id: attr.Id, - Prefixes: attr.Prefixes, - } - if err := self.Getter.SetDestination(d); err != nil { - return err - } - return nil -} - -type AttrGetBalance struct { - Tenant string - Account string - BalanceId string - Direction string -} - -// Get balance -func (self *Apier) GetBalance(attr *AttrGetBalance, reply *float64) error { - tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) - userBalance, err := self.Getter.GetUserBalance(tag) - if err != nil { - return err - } - - if attr.Direction == "" { - attr.Direction = rater.OUTBOUND - } - - if balance, balExists := userBalance.BalanceMap[attr.BalanceId+attr.Direction]; !balExists { - // No match, balanceId not found - return errors.New(utils.ERR_NOT_FOUND) - } else { - *reply = balance - } - return nil -} - -type AttrAddBalance struct { - Tenant string - Account string - BalanceId string - Direction string - Value float64 -} - -func (self *Apier) AddBalance(attr *AttrAddBalance, reply *float64) error { - // what storage instance do we use? - tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) - - at := &rater.ActionTiming{ - UserBalanceIds: []string{tag}, - } - - if attr.Direction == "" { - attr.Direction = rater.OUTBOUND - } - - at.SetActions(rater.Actions{&rater.Action{BalanceId: attr.BalanceId, Direction: attr.Direction, Units: attr.Value}}) - - if err := at.Execute(); err != nil { - return err - } - // what to put in replay? - return nil -} - -type AttrExecuteAction struct { - Direction string - Tenant string - Account string - BalanceId string - ActionsId string -} - -func (self *Apier) ExecuteAction(attr *AttrExecuteAction, reply *float64) error { - tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) - at := &rater.ActionTiming{ - UserBalanceIds: []string{tag}, - ActionsId: attr.ActionsId, - } - - if err := at.Execute(); err != nil { - return err - } - // what to put in replay - return nil -} - -type AttrSetRatingProfile struct { - Direction string - Tenant string - TOR string - Subject string - RatingProfileId string -} - -func (self *Apier) SetRatingProfile(attr *AttrSetRatingProfile, reply *float64) error { - cd := &rater.CallDescriptor{ - Direction: attr.Direction, - Tenant: attr.Tenant, - TOR: attr.TOR, - Subject: attr.Subject, - } - subject, err := self.Getter.GetRatingProfile(cd.GetKey()) - if err != nil { - return err - } - rp, err := self.Getter.GetRatingProfile(attr.RatingProfileId) - if err != nil { - return err - } - subject.DestinationMap = rp.DestinationMap - err = self.Getter.SetRatingProfile(subject) - return err -} - -type AttrActionTrigger struct { - Tenant string - Account string - Direction string - BalanceId string - ThresholdValue float64 - DestinationId string - Weight float64 - ActionsId string -} - -func (self *Apier) AddTriggeredAction(attr *AttrActionTrigger, reply *float64) error { - if attr.Direction == "" { - attr.Direction = rater.OUTBOUND - } - - at := &rater.ActionTrigger{ - Id: utils.GenUUID(), - BalanceId: attr.BalanceId, - Direction: attr.Direction, - ThresholdValue: attr.ThresholdValue, - DestinationId: attr.DestinationId, - Weight: attr.Weight, - ActionsId: attr.ActionsId, - Executed: false, - } - - tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) - var dbErr error - rater.AccLock.Guard(tag, func() (float64, error) { - userBalance, err := self.Getter.GetUserBalance(tag) - if err != nil { - dbErr = err - return 0, err - } - - userBalance.ActionTriggers = append(userBalance.ActionTriggers, at) - - if err = self.Getter.SetUserBalance(userBalance); err != nil { - dbErr = err - return 0, err - } - return 0, nil - }) - - return dbErr -} diff --git a/cmd/cgr-rater/cgr-rater.go b/cmd/cgr-rater/cgr-rater.go index b2b618a7c..30f1f8e7a 100644 --- a/cmd/cgr-rater/cgr-rater.go +++ b/cmd/cgr-rater/cgr-rater.go @@ -64,7 +64,7 @@ var ( err error ) -func listenToRPCRequests(rpcResponder interface{}, rpcAddress string, rpc_encoding string, getter rater.DataStorage, loggerDb rater.DataStorage) { +func listenToRPCRequests(rpcResponder interface{}, apier *apier.Apier, rpcAddress string, rpc_encoding string, getter rater.DataStorage, loggerDb rater.DataStorage) { l, err := net.Listen("tcp", rpcAddress) if err != nil { rater.Logger.Crit(fmt.Sprintf(" Could not listen to %v: %v", rpcAddress, err)) @@ -75,7 +75,7 @@ func listenToRPCRequests(rpcResponder interface{}, rpcAddress string, rpc_encodi rater.Logger.Info(fmt.Sprintf(" Listening for incomming RPC requests on %v", l.Addr())) rpc.Register(rpcResponder) - rpc.Register(&apier.Apier{StorDb: loggerDb, Getter: getter}) + rpc.Register(apier) var serveFunc func(io.ReadWriteCloser) if rpc_encoding == JSON { serveFunc = jsonrpc.ServeConn @@ -296,15 +296,16 @@ func main() { go stopRaterSingnalHandler() } responder := &rater.Responder{ExitChan: exitChan} + apier := &apier.Apier{StorDb: loggerDb, Getter: getter} if cfg.RaterEnabled && !cfg.BalancerEnabled && cfg.RaterListen != INTERNAL { rater.Logger.Info(fmt.Sprintf("Starting CGRateS Rater on %s.", cfg.RaterListen)) - go listenToRPCRequests(responder, cfg.RaterListen, cfg.RPCEncoding, getter, loggerDb) + go listenToRPCRequests(responder, apier, cfg.RaterListen, cfg.RPCEncoding, getter, loggerDb) } if cfg.BalancerEnabled { rater.Logger.Info(fmt.Sprintf("Starting CGRateS Balancer on %s.", cfg.BalancerListen)) go stopBalancerSingnalHandler() responder.Bal = bal - go listenToRPCRequests(responder, cfg.BalancerListen, cfg.RPCEncoding, getter, loggerDb) + go listenToRPCRequests(responder, apier, cfg.BalancerListen, cfg.RPCEncoding, getter, loggerDb) if cfg.RaterEnabled { rater.Logger.Info("Starting internal rater.") bal.AddClient("local", new(rater.ResponderWorker)) @@ -316,6 +317,7 @@ func main() { go func() { sched := scheduler.NewScheduler() go reloadSchedulerSingnalHandler(sched, getter) + apier.Sched = sched sched.LoadActionTimings(getter) sched.Loop() }() diff --git a/docs/apicalls.rst b/docs/apicalls.rst index d9070d1e7..adfb4ae6e 100644 --- a/docs/apicalls.rst +++ b/docs/apicalls.rst @@ -428,6 +428,22 @@ AddTriggeredAction Example AddTriggeredAction(attr \*AttrActionTrigger, reply \*float64) +AddAcount ++++++++++ + +:: + + type AttrAccount struct { + Tenant string + Direction string + Account string + Type string // prepaid-postpaid + ActionTimingsId string + } + +Example + AddAccount(attr \*AttrAccount, reply \*float64) + SetRatingProfile ++++++++++++++++ @@ -440,7 +456,7 @@ Sets the rating profile for the specified subject. Tenant string TOR string Subject string - RatingProfileId string + SourceSubject string } Example diff --git a/rater/action.go b/rater/action.go index c28c41885..d9c317d97 100644 --- a/rater/action.go +++ b/rater/action.go @@ -36,31 +36,45 @@ type Action struct { MinuteBucket *MinuteBucket } +const ( + LOG = "LOG" + RESET_TRIGGERS = "RESET_TRIGGERS" + SET_POSTPAID = "SET_POSTPAID" + RESET_POSTPAID = "RESET_POSTPAID" + SET_PREPAID = "SET_PREPAID" + RESET_PREPAID = "RESET_PREPAID" + TOPUP_RESET = "TOPUP_RESET" + TOPUP = "TOPUP" + DEBIT = "DEBIT" + RESET_COUNTER = "RESET_COUNTER" + RESET_COUNTERS = "RESET_COUNTERS" +) + type actionTypeFunc func(*UserBalance, *Action) error func getActionFunc(typ string) (actionTypeFunc, bool) { switch typ { - case "LOG": + case LOG: return logAction, true - case "RESET_TRIGGERS": + case RESET_TRIGGERS: return resetTriggersAction, true - case "SET_POSTPAID": + case SET_POSTPAID: return setPostpaidAction, true - case "RESET_POSTPAID": + case RESET_POSTPAID: return resetPostpaidAction, true - case "SET_PREPAID": + case SET_PREPAID: return setPrepaidAction, true - case "RESET_PREPAID": + case RESET_PREPAID: return resetPrepaidAction, true - case "TOPUP_RESET": + case TOPUP_RESET: return topupResetAction, true - case "TOPUP": + case TOPUP: return topupAction, true - case "DEBIT": + case DEBIT: return debitAction, true - case "RESET_COUNTER": + case RESET_COUNTER: return resetCounterAction, true - case "RESET_COUNTERS": + case RESET_COUNTERS: return resetCountersAction, true } return nil, false