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"