diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index 6f15d29c3..ee2ab2b36 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -26,7 +26,7 @@ import ( "time" ) -type AttrAcntActionTimings struct { +type AttrAcntAction struct { Tenant string Account string Direction string @@ -45,7 +45,7 @@ type AccountActionTiming struct { NextExecTime time.Time // Next execution time } -func (self *ApierV1) GetAccountActionTimings(attrs AttrAcntActionTimings, reply *[]*AccountActionTiming) error { +func (self *ApierV1) GetAccountActionTimings(attrs AttrAcntAction, reply *[]*AccountActionTiming) error { if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Direction"}); len(missing) != 0 { return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) } @@ -109,7 +109,7 @@ func (self *ApierV1) RemActionTiming(attrs AttrRemActionTiming, reply *string) e } // Returns a list of ActionTriggers on an account -func (self *ApierV1) GetAccountActionTriggers(attrs AttrAcntActionTimings, reply *engine.ActionTriggerPriotityList) error { +func (self *ApierV1) GetAccountActionTriggers(attrs AttrAcntAction, reply *engine.ActionTriggerPriotityList) error { if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Direction"}); len(missing) != 0 { return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) } diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go index 9c544421e..0c3ff8b51 100644 --- a/apier/v1/apier_local_test.go +++ b/apier/v1/apier_local_test.go @@ -31,6 +31,9 @@ import ( "reflect" "testing" "time" + "net/http" + "net/url" + "strings" ) // ToDo: Replace rpc.Client with internal rpc server and Apier using internal map as both data and stor so we can run the tests non-local @@ -119,7 +122,7 @@ func TestStartEngine(t *testing.T) { t.Fatal("Cannot find cgr-engine executable") } exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it - engine := exec.Command(enginePath, "-rater", "-scheduler", "-config", path.Join(*dataDir, "conf", "cgrates.cfg")) + engine := exec.Command(enginePath, "-rater", "-scheduler", "-cdrs", "-mediator", "-config", path.Join(*dataDir, "conf", "cgrates.cfg")) if err := engine.Start(); err != nil { t.Fatal("Cannot start cgr-engine: ", err.Error()) } @@ -984,23 +987,67 @@ func TestApierAddTriggeredAction(t *testing.T) { *attrs2 = *attrs attrs2.Account = "dan3" // 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.ExecuteAction", attrs2, &reply2); err == nil || reply2 == "OK" { + if err := rater.Call("ApierV1.AddTriggeredAction", attrs2, &reply2); err == nil || reply2 == "OK" { t.Error("Expecting error on ApierV1.AddTriggeredAction.", err, reply2) } } + + +// Test here AddTriggeredAction +func TestApierGetAccountActionTriggers(t *testing.T) { + if !*testLocal { + return + } + var reply engine.ActionTriggerPriotityList + req := AttrAcntAction{Tenant: "cgrates.org", Account:"dan2", Direction: "*out"} + if err := rater.Call("ApierV1.GetAccountActionTriggers", req, &reply); err != nil { + t.Error("Got error on ApierV1.GetAccountActionTimings: ", err.Error()) + } else if len(reply) != 1 || reply[0].ActionsId != "WARN_VIA_HTTP" { + t.Errorf("Unexpected action triggers received %v", reply) + } +} + +// Test here RemAccountActionTriggers +func TestApierRemAccountActionTriggers(t *testing.T) { + if !*testLocal { + return + } + // Test first get so we can steal the id which we need to remove + var reply engine.ActionTriggerPriotityList + req := AttrAcntAction{Tenant: "cgrates.org", Account:"dan2", Direction: "*out"} + if err := rater.Call("ApierV1.GetAccountActionTriggers", req, &reply); err != nil { + t.Error("Got error on ApierV1.GetAccountActionTimings: ", err.Error()) + } else if len(reply) != 1 || reply[0].ActionsId != "WARN_VIA_HTTP" { + t.Errorf("Unexpected action triggers received %v", reply) + } + var rmReply string + rmReq := AttrRemAcntActionTriggers{Tenant: "cgrates.org", Account:"dan2", Direction: "*out", ActionTriggerId: reply[0].Id} + if err := rater.Call("ApierV1.RemAccountActionTriggers", rmReq, &rmReply); err != nil { + t.Error("Got error on ApierV1.RemActionTiming: ", err.Error()) + } else if rmReply != OK { + t.Error("Unexpected answer received", rmReply) + } + if err := rater.Call("ApierV1.GetAccountActionTriggers", req, &reply); err != nil { + t.Error("Got error on ApierV1.GetAccountActionTriggers: ", err.Error()) + } else if len(reply) != 0 { + t.Errorf("Unexpected action triggers received %v", reply) + } +} + + // Test here AddAccount func TestApierAddAccount(t *testing.T) { if !*testLocal { return } - //reply := "" - attrs := &AttrAddAccount{Tenant: "cgrates.org", Direction: "*out", Account: "dan4", Type: "prepaid", ActionTimingsId: "PREPAID_10"} - //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) - //} + 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 @@ -1011,6 +1058,45 @@ func TestApierAddAccount(t *testing.T) { } } +// Test here GetAccountActionTimings +func TestApierGetAccountActionTimings(t *testing.T) { + if !*testLocal { + return + } + var reply []*AccountActionTiming + req := AttrAcntAction{Tenant: "cgrates.org", Account:"dan4", 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 { + t.Error("Unexpected action timings received") + } else { + if reply[0].ActionTimingsId != "ATMS_1" { + t.Errorf("Unexpected ActionTImingsId received") + } + } +} + +// Test here RemActionTiming +func TestApierRemActionTiming(t *testing.T) { + if !*testLocal { + return + } + var rmReply string + rmReq := AttrRemActionTiming{ActionTimingsId: "ATMS_1", Tenant: "cgrates.org", Account:"dan4", Direction: "*out"} + if err := rater.Call("ApierV1.RemActionTiming", rmReq, &rmReply); err != nil { + t.Error("Got error on ApierV1.RemActionTiming: ", err.Error()) + } else if rmReply != OK { + t.Error("Unexpected answer received", rmReply) + } + var reply []*AccountActionTiming + req := AttrAcntAction{Tenant: "cgrates.org", Account:"dan4", 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) != 0 { + t.Error("Action timings was not removed") + } +} + // Test here GetBalance func TestApierGetBalance(t *testing.T) { if !*testLocal { @@ -1080,6 +1166,52 @@ func TestResponderGetCost(t *testing.T) { } } +func TestCdrServer(t *testing.T) { + httpClient := new(http.Client) + cdrForm1 := url.Values{"accid": []string{"dsafdsaf"}, "cdrhost": []string{"192.168.1.1"}, "reqtype": []string{"rated"}, "direction": []string{"*out"}, + "tenant": []string{"cgrates.org"}, "tor": []string{"call"}, "account": []string{"1001"}, "subject": []string{"1001"}, "destination": []string{"1002"}, + "answer_time": []string{"2013-11-07T08:42:26Z"}, "duration": []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}} + cdrForm2 := url.Values{"accid": []string{"adsafdsaf"}, "cdrhost": []string{"192.168.1.1"}, "reqtype": []string{"rated"}, "direction": []string{"*out"}, + "tenant": []string{"cgrates.org"}, "tor": []string{"call"}, "account": []string{"1001"}, "subject": []string{"1001"}, "destination": []string{"1002"}, + "answer_time": []string{"2013-11-07T08:42:26Z"}, "duration": []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}} + for _, cdrForm := range []url.Values{cdrForm1, cdrForm2} { + cdrForm.Set(utils.CDRSOURCE, engine.TEST_SQL) + if _, err := httpClient.PostForm(fmt.Sprintf("http://%s/cgr", cfg.CdrcCdrs), cdrForm); err != nil { + t.Error(err.Error()) + } + } +} + +func TestExportCdrsToFile(t *testing.T) { + var reply *utils.ExportedFileCdrs + req := utils.AttrExpFileCdrs{} + if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err == nil || !strings.HasPrefix(err.Error(), utils.ERR_MANDATORY_IE_MISSING) { + t.Error("Failed to detect missing parameter") + } + req.CdrFormat = utils.CDRE_DRYRUN + expectReply := &utils.ExportedFileCdrs{NumberOfRecords: 2} + if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err != nil { + t.Error(err.Error()) + } else if !reflect.DeepEqual(reply, expectReply) { + t.Errorf("Unexpected reply: %v", reply) + } + /* Need to implement temporary file writing in order to test removal from db, not possible on DRYRUN + req.RemoveFromDb = true + if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err != nil { + t.Error(err.Error()) + } else if !reflect.DeepEqual(reply, expectReply) { + t.Errorf("Unexpected reply: %v", reply) + } + expectReply.NumberOfRecords = 0 // We should have deleted previously + if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err != nil { + t.Error(err.Error()) + } else if !reflect.DeepEqual(reply, expectReply) { + t.Errorf("Unexpected reply: %v", reply) + } + */ +} + + // Simply kill the engine after we are done with tests within this file func TestStopEngine(t *testing.T) { if !*testLocal { diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index 653105811..bcf04aae5 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -65,6 +65,7 @@ var ( schedEnabled = flag.Bool("scheduler", false, "Enforce starting of the scheduler daemon overwriting config") cdrsEnabled = flag.Bool("cdrs", false, "Enforce starting of the cdrs daemon overwriting config") cdrcEnabled = flag.Bool("cdrc", false, "Enforce starting of the cdrc service overwriting config") + mediatorEnabled = flag.Bool("mediator", false, "Enforce starting of the mediator service overwriting config") pidFile = flag.String("pid", "", "Write pid file") bal = balancer2go.NewBalancer() exitChan = make(chan bool) @@ -373,6 +374,9 @@ func main() { if *cdrcEnabled { cfg.CdrcEnabled = *cdrcEnabled } + if *mediatorEnabled { + cfg.MediatorEnabled = *mediatorEnabled + } if cfg.RaterEnabled { if err := ratingDb.CacheRating(nil, nil, nil); err != nil { engine.Logger.Crit(fmt.Sprintf("Cache rating error: %v", err)) diff --git a/engine/storage_sql.go b/engine/storage_sql.go index b4c1a36ac..539c41a29 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -758,11 +758,12 @@ func (self *SQLStorage) GetRatedCdrs(timeStart, timeEnd time.Time) ([]*utils.Rat } defer rows.Close() for rows.Next() { - var cgrid, accid, cdrhost, cdrsrc, reqtype, direction, tenant, tor, account, subject, destination, runid string + var cgrid, accid, cdrhost, cdrsrc, reqtype, direction, tenant, tor, account, subject, destination string var extraFields []byte var answerTime time.Time var duration int64 - var cost float64 + var runid sql.NullString // So we can export unmediated CDRs + var cost sql.NullFloat64 // So we can export unmediated CDRs var extraFieldsMp map[string]string if err := rows.Scan(&cgrid, &accid, &cdrhost, &cdrsrc, &reqtype, &direction, &tenant, &tor, &account, &subject, &destination, &answerTime, &duration, &extraFields, &runid, &cost); err != nil { @@ -774,7 +775,7 @@ func (self *SQLStorage) GetRatedCdrs(timeStart, timeEnd time.Time) ([]*utils.Rat storCdr := &utils.RatedCDR{ CgrId: cgrid, AccId: accid, CdrHost: cdrhost, CdrSource: cdrsrc, ReqType: reqtype, Direction: direction, Tenant: tenant, TOR: tor, Account: account, Subject: subject, Destination: destination, AnswerTime: answerTime, Duration: time.Duration(duration), - ExtraFields: extraFieldsMp, MediationRunId: runid, Cost: cost, + ExtraFields: extraFieldsMp, MediationRunId: runid.String, Cost: cost.Float64, } cdrs = append(cdrs, storCdr) }