diff --git a/engine/action.go b/engine/action.go
index 16003e322..3a5d30631 100644
--- a/engine/action.go
+++ b/engine/action.go
@@ -106,6 +106,7 @@ func getActionFunc(typ string) (actionTypeFunc, bool) {
utils.MetaExport: export,
utils.MetaResetThreshold: resetThreshold,
utils.MetaResetStatQueue: resetStatQueue,
+ utils.MetaRemoteSetAccount: remoteSetAccount,
}
f, exists := actionFuncMap[typ]
return f, exists
@@ -1069,3 +1070,24 @@ func resetStatQueue(ub *Account, a *Action, acs Actions, extraData interface{})
return connMgr.Call(config.CgrConfig().SchedulerCfg().StatSConns, nil,
utils.StatSv1ResetStatQueue, args, &rply)
}
+
+func remoteSetAccount(ub *Account, a *Action, acs Actions, extraData interface{}) (err error) {
+ client := &http.Client{Transport: httpPstrTransport}
+ var resp *http.Response
+ req := new(bytes.Buffer)
+ if err = json.NewEncoder(req).Encode(ub); err != nil {
+ return
+ }
+ if resp, err = client.Post(a.ExtraParameters, "application/json", req); err != nil {
+ return
+ }
+ acc := new(Account)
+ err = json.NewDecoder(resp.Body).Decode(acc)
+ if err != nil {
+ return
+ }
+ if len(acc.BalanceMap) != 0 {
+ *ub = *acc
+ }
+ return
+}
diff --git a/engine/action_plan.go b/engine/action_plan.go
index 022a1d3f9..12fbd04e0 100644
--- a/engine/action_plan.go
+++ b/engine/action_plan.go
@@ -204,9 +204,15 @@ func (at *ActionTiming) Execute(successActions, failedActions chan *Action) (err
for accID := range at.accountIDs {
_, err = guardian.Guardian.Guard(func() (interface{}, error) {
acc, err := dm.GetAccount(accID)
- if err != nil {
- utils.Logger.Warning(fmt.Sprintf("Could not get account id: %s. Skipping!", accID))
- return 0, err
+ if err != nil { // create account
+ if err != utils.ErrNotFound {
+ utils.Logger.Warning(fmt.Sprintf("Could not get account id: %s. Skipping!", accID))
+ return 0, err
+ }
+ err = nil
+ acc = &Account{
+ ID: accID,
+ }
}
transactionFailed := false
removeAccountActionFound := false
diff --git a/engine/actions_test.go b/engine/actions_test.go
index d66e23472..13dc65feb 100644
--- a/engine/actions_test.go
+++ b/engine/actions_test.go
@@ -19,6 +19,9 @@ package engine
import (
"fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
"reflect"
"strings"
"testing"
@@ -2625,13 +2628,13 @@ func TestCdrLogAction(t *testing.T) {
acc := &Account{
ID: "cgrates.org:1001",
BalanceMap: map[string]Balances{
- utils.MONETARY: Balances{
+ utils.MONETARY: {
&Balance{Value: 20},
},
},
UnitCounters: UnitCounters{
utils.MONETARY: []*UnitCounter{
- &UnitCounter{
+ {
Counters: CounterFilters{
&CounterFilter{Value: 1},
},
@@ -2701,6 +2704,71 @@ func TestCdrLogAction(t *testing.T) {
}
}
+func TestRemoteSetAccountAction(t *testing.T) {
+ expError := `Post "127.1.0.11//": unsupported protocol scheme ""`
+ if err = remoteSetAccount(nil, &Action{ExtraParameters: "127.1.0.11//"}, nil, nil); err == nil ||
+ err.Error() != expError {
+ t.Fatalf("Expected error: %s, received: %v", expError, err)
+ }
+ expError = `json: unsupported type: func()`
+ if err = remoteSetAccount(&Account{
+ ActionTriggers: ActionTriggers{{
+ Balance: &BalanceFilter{
+ Value: &utils.ValueFormula{
+ Params: map[string]interface{}{utils.VOICE: func() {}},
+ },
+ },
+ }},
+ }, &Action{ExtraParameters: "127.1.0.11//"}, nil, nil); err == nil ||
+ err.Error() != expError {
+ t.Fatalf("Expected error: %s, received: %v", expError, err)
+ }
+
+ ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rw.Write([]byte("5")) }))
+ acc := &Account{ID: "1001"}
+ expError = `json: cannot unmarshal number into Go value of type engine.Account`
+ if err = remoteSetAccount(acc, &Action{ExtraParameters: ts.URL}, nil, nil); err == nil ||
+ err.Error() != expError {
+ t.Fatalf("Expected error: %s, received: %v", expError, err)
+ }
+ exp := &Account{ID: "1001"}
+ if !reflect.DeepEqual(exp, acc) {
+ t.Errorf("Expected: %s,received: %s", utils.ToJSON(exp), utils.ToJSON(acc))
+ }
+ ts.Close()
+
+ acc = &Account{ID: "1001"}
+ exp = &Account{
+ ID: "1001",
+ BalanceMap: map[string]Balances{
+ utils.META_VOICE: {{
+ ID: "money",
+ Value: 15,
+ }},
+ },
+ }
+ ts = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+ accStr := utils.ToJSON(acc) + "\n"
+ val, err := ioutil.ReadAll(r.Body)
+ r.Body.Close()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if string(val) != accStr {
+ t.Errorf("Expected %q,received: %q", accStr, string(val))
+ return
+ }
+ rw.Write([]byte(utils.ToJSON(exp)))
+ }))
+ if err = remoteSetAccount(acc, &Action{ExtraParameters: ts.URL}, nil, nil); err != nil {
+ t.Fatal(err)
+ } else if !reflect.DeepEqual(exp, acc) {
+ t.Errorf("Expected: %s,received: %s", utils.ToJSON(exp), utils.ToJSON(acc))
+ }
+ ts.Close()
+}
+
/**************** Benchmarks ********************************/
func BenchmarkUUID(b *testing.B) {
diff --git a/engine/z_actions_it_test.go b/engine/z_actions_it_test.go
index fdd5b26ea..fa14a588b 100644
--- a/engine/z_actions_it_test.go
+++ b/engine/z_actions_it_test.go
@@ -20,6 +20,9 @@ along with this program. If not, see
package engine
import (
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
"net/rpc"
"path"
"reflect"
@@ -53,6 +56,7 @@ var (
testActionsitThresholdPostEvent,
testActionsitSetSDestinations,
testActionsitresetAccountCDR,
+ testActionsitremoteSetAccount,
testActionsitStopCgrEngine,
}
)
@@ -925,3 +929,65 @@ func testActionsitStopCgrEngine(t *testing.T) {
t.Error(err)
}
}
+
+func testActionsitremoteSetAccount(t *testing.T) {
+ var reply string
+ account := "remote1234"
+ accID := utils.ConcatenatedKey("cgrates.org", account)
+ acc := &Account{
+ ID: accID,
+ }
+ exp := &Account{
+ ID: accID,
+ BalanceMap: map[string]Balances{
+ utils.MONETARY: []*Balance{{
+ Value: 20,
+ Weight: 10,
+ }},
+ },
+ }
+ ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+ accStr := utils.ToJSON(acc) + "\n"
+ val, err := ioutil.ReadAll(r.Body)
+ r.Body.Close()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if string(val) != accStr {
+ t.Errorf("Expected %q,received: %q", accStr, string(val))
+ return
+ }
+ rw.Write([]byte(utils.ToJSON(exp)))
+ }))
+
+ defer ts.Close()
+ attrsAA := &utils.AttrSetActions{
+ ActionsId: "remoteSetAccountCDR",
+ Actions: []*utils.TPAction{
+ {Identifier: utils.MetaRemoteSetAccount, ExtraParameters: ts.URL, Weight: 20.0},
+ },
+ }
+ if err := actsLclRpc.Call(utils.APIerSv2SetActions, attrsAA, &reply); err != nil && err.Error() != utils.ErrExists.Error() {
+ t.Error("Got error on APIerSv2.SetActions: ", err.Error())
+ } else if reply != utils.OK {
+ t.Errorf("Calling APIerSv2.SetActions received: %s", reply)
+ }
+
+ attrsEA := &utils.AttrExecuteAction{Tenant: "cgrates.org", Account: account, ActionsId: attrsAA.ActionsId}
+ if err := actsLclRpc.Call(utils.APIerSv1ExecuteAction, attrsEA, &reply); err != nil {
+ t.Error("Got error on APIerSv1.ExecuteAction: ", err.Error())
+ } else if reply != utils.OK {
+ t.Errorf("Calling APIerSv1.ExecuteAction received: %s", reply)
+ }
+
+ var acc2 Account
+ attrs2 := &utils.AttrGetAccount{Account: account}
+ if err := actsLclRpc.Call(utils.APIerSv2GetAccount, attrs2, &acc2); err != nil {
+ t.Fatal("Got error on APIerSv1.GetAccount: ", err.Error())
+ }
+ acc2.UpdateTime = exp.UpdateTime
+ if utils.ToJSON(exp) != utils.ToJSON(acc2) {
+ t.Errorf("Expected: %s,received: %s", utils.ToJSON(exp), utils.ToJSON(acc2))
+ }
+}
diff --git a/packages/debian/changelog b/packages/debian/changelog
index 6120d78a9..882a25477 100644
--- a/packages/debian/changelog
+++ b/packages/debian/changelog
@@ -132,6 +132,7 @@ cgrates (0.11.0~dev) UNRELEASED; urgency=medium
* [RouteS] Add new field RouteRateProfileIDs in RateProfiles.csv
* [DispatcherS] Removed connection pool from DispatcherHost structure
* [DispatcherS] Updated *broadcast, *broadcast_sync and *broadcast_async to behave similar to RPCPool
+ * [ActionsS] Added *remote_set_account action
-- DanB Wed, 19 Feb 2020 13:25:52 +0200
diff --git a/utils/consts.go b/utils/consts.go
index ca2ad5401..c4e03dcc5 100755
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -1002,6 +1002,7 @@ const (
MetaCDRAccount = "*reset_account_cdr"
MetaResetThreshold = "*reset_threshold"
MetaResetStatQueue = "*reset_stat_queue"
+ MetaRemoteSetAccount = "*remote_set_account"
ActionID = "ActionID"
ActionType = "ActionType"
ActionValue = "ActionValue"