Implement APIerSv1.TransferBalance API

This commit is contained in:
ionutboangiu
2024-02-16 12:53:22 -05:00
committed by Dan Christian Bogos
parent 41976e5721
commit 0c32f1761c
4 changed files with 152 additions and 6 deletions

View File

@@ -19,7 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package v1
import (
"encoding/json"
"errors"
"fmt"
"math"
"slices"
"strings"
@@ -712,6 +714,92 @@ func (apierSv1 *APIerSv1) RemoveBalances(ctx *context.Context, attr *utils.AttrS
return nil
}
// TransferBalance sets a temporary *transfer_balance action, which will be executed immediately.
func (apierSv1 *APIerSv1) TransferBalance(ctx *context.Context, attr utils.AttrTransferBalance, reply *string) (err error) {
if missing := utils.MissingStructFields(&attr, []string{
utils.SrcAccountID, utils.SrcBalanceID,
utils.DestAccountID, utils.DestBalanceID,
utils.Units, utils.BalanceType}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
if attr.Tenant == "" {
attr.Tenant = apierSv1.Config.GeneralCfg().DefaultTenant
}
// Set *transfer_balance action.
actionID := fmt.Sprintf("tmp_act_%s", utils.UUIDSha1Prefix())
if !attr.Overwrite {
var exists bool
if exists, err = apierSv1.DataManager.HasData(utils.ActionPrefix, actionID, ""); err != nil {
return utils.NewErrServerError(err)
} else if exists {
return utils.ErrExists
}
}
var extraParams []byte
extraParams, err = json.Marshal(map[string]string{
utils.DestAccountID: attr.Tenant + ":" + attr.DestAccountID,
utils.DestBalanceID: attr.DestBalanceID,
})
if err != nil {
return utils.NewErrServerError(err)
}
action := &engine.Action{
Id: actionID,
ActionType: utils.MetaTransferBalance,
ExtraParameters: string(extraParams),
Balance: &engine.BalanceFilter{
ID: utils.StringPointer(attr.SrcBalanceID),
Type: utils.StringPointer(attr.BalanceType),
Value: &utils.ValueFormula{Static: attr.Units},
},
}
if err = apierSv1.DataManager.SetActions(actionID, engine.Actions{action}); err != nil {
return utils.NewErrServerError(err)
}
// Remove the action.
defer func() {
if removeErr := apierSv1.DataManager.RemoveActions(actionID); err != nil {
err = errors.Join(err, removeErr)
return
}
if reloadCacheErr := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
ActionIDs: []string{actionID},
}, reply); reloadCacheErr != nil {
err = errors.Join(err, reloadCacheErr)
return
}
if setLoadIDErr := apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheActions: time.Now().UnixNano()}); setLoadIDErr != nil {
err = errors.Join(err, setLoadIDErr)
return
}
*reply = utils.OK
}()
if err = apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
ActionIDs: []string{actionID},
}, reply); err != nil {
return utils.NewErrServerError(err)
}
//generate a loadID for CacheActions and store it in database
if err = apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheActions: time.Now().UnixNano()}); err != nil {
return utils.NewErrServerError(err)
}
// Execute the action.
at := &engine.ActionTiming{
ActionsID: actionID,
}
at.SetAccountIDs(utils.StringMap{utils.ConcatenatedKey(attr.Tenant, attr.SrcAccountID): true})
if err = at.Execute(apierSv1.FilterS); err != nil {
return utils.NewErrServerError(err)
}
return nil
}
func (apierSv1 *APIerSv1) GetAccountsCount(ctx *context.Context, attr *utils.TenantWithAPIOpts, reply *int) (err error) {
tnt := attr.Tenant
if tnt == utils.EmptyString {

View File

@@ -21,7 +21,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package general_tests
import (
"bytes"
"sort"
"testing"
@@ -81,13 +80,10 @@ ACT_TOPUP_DEST,*topup_reset,,,balance_dest,*monetary,,*any,,,*unlimited,,10,10,f
ACT_TRANSFER,*transfer_balance,"{""DestAccountID"":""cgrates.org:ACC_DEST"",""DestBalanceID"":""balance_dest""}",,balance_src,*monetary,,,,,*unlimited,,4,,,,`,
}
buf := &bytes.Buffer{}
testEnv := TestEnvironment{
Name: "TestTransferBalance",
// Encoding: *encoding,
Name: "TestTransferBalance",
ConfigJSON: content,
TpFiles: tpFiles,
LogBuffer: buf,
}
client, _, shutdown, err := testEnv.Setup(t, *waitRater)
if err != nil {
@@ -134,7 +130,7 @@ ACT_TRANSFER,*transfer_balance,"{""DestAccountID"":""cgrates.org:ACC_DEST"",""De
}
})
t.Run("CheckFinalBalances", func(t *testing.T) {
t.Run("CheckBalancesAfterActionExecute", func(t *testing.T) {
var acnts []*engine.Account
if err := client.Call(context.Background(), utils.APIerSv2GetAccounts,
&utils.AttrGetAccounts{
@@ -163,4 +159,49 @@ ACT_TRANSFER,*transfer_balance,"{""DestAccountID"":""cgrates.org:ACC_DEST"",""De
t.Errorf("received account with unexpected balance: %v", balance)
}
})
t.Run("TransferBalanceByAPI", func(t *testing.T) {
var reply string
if err := client.Call(context.Background(), utils.APIerSv1TransferBalance, utils.AttrTransferBalance{
Tenant: "cgrates.org",
SrcAccountID: "ACC_SRC",
SrcBalanceID: "balance_src",
DestAccountID: "ACC_DEST",
DestBalanceID: "balance_dest",
Units: 2,
BalanceType: utils.MetaMonetary,
}, &reply); err != nil {
t.Error(err)
}
})
t.Run("CheckBalancesAfterTransferBalanceAPI", func(t *testing.T) {
var acnts []*engine.Account
if err := client.Call(context.Background(), utils.APIerSv2GetAccounts,
&utils.AttrGetAccounts{
Tenant: "cgrates.org",
}, &acnts); err != nil {
t.Error(err)
}
if len(acnts) != 2 {
t.Fatal("expecting 2 accounts to be retrieved")
}
sort.Slice(acnts, func(i, j int) bool {
return acnts[i].ID > acnts[j].ID
})
if len(acnts[0].BalanceMap) != 1 || len(acnts[0].BalanceMap[utils.MetaMonetary]) != 1 {
t.Errorf("expected account to have only one balance of type *monetary, received %v", acnts[0])
}
balance := acnts[0].BalanceMap[utils.MetaMonetary][0]
if balance.ID != "balance_src" || balance.Value != 4 {
t.Errorf("received account with unexpected balance: %v", balance)
}
if len(acnts[1].BalanceMap) != 1 || len(acnts[1].BalanceMap[utils.MetaMonetary]) != 1 {
t.Errorf("expected account to have only one balance of type *monetary, received %v", acnts[1])
}
balance = acnts[1].BalanceMap[utils.MetaMonetary][0]
if balance.ID != "balance_dest" || balance.Value != 16 {
t.Errorf("received account with unexpected balance: %v", balance)
}
})
}

View File

@@ -879,6 +879,18 @@ type AttrBalance struct {
Cdrlog bool
}
type AttrTransferBalance struct {
Tenant string
SrcAccountID string
SrcBalanceID string
DestAccountID string
DestBalanceID string
Units float64
BalanceType string
Overwrite bool
Cdrlog bool
}
// TPResourceProfile is used in APIs to manage remotely offline ResourceProfile
type TPResourceProfile struct {
TPid string

View File

@@ -491,6 +491,10 @@ const (
EventSource = "EventSource"
AccountID = "AccountID"
AccountIDs = "AccountIDs"
SrcAccountID = "SrcAccountID"
DestAccountID = "DestAccountID"
SrcBalanceID = "SrcBalanceID"
DestBalanceID = "DestBalanceID"
ResourceID = "ResourceID"
TotalUsage = "TotalUsage"
StatID = "StatID"
@@ -1319,6 +1323,7 @@ const (
APIerSv1ExportToFolder = "APIerSv1.ExportToFolder"
APIerSv1GetCost = "APIerSv1.GetCost"
APIerSv1SetBalance = "APIerSv1.SetBalance"
APIerSv1TransferBalance = "APIerSv1.TransferBalance"
APIerSv1GetFilter = "APIerSv1.GetFilter"
APIerSv1GetFilterIndexes = "APIerSv1.GetFilterIndexes"
APIerSv1RemoveFilterIndexes = "APIerSv1.RemoveFilterIndexes"