UserS methods in both CallDescriptor and StoredCdr, fixes #129

This commit is contained in:
DanB
2015-07-29 17:11:40 +02:00
7 changed files with 285 additions and 71 deletions

View File

@@ -4,9 +4,11 @@
Loading **CGRateS** Tariff Plans
--------------------------------
Before proceeding to this step, you should have **CGRateS** installed and started with custom configuration, depending on the tutorial you have followed.
Before proceeding to this step, you should have **CGRateS** installed and
started with custom configuration, depending on the tutorial you have followed.
For our tutorial we load again prepared data out of shared folder, containing following rules:
For our tutorial we load again prepared data out of shared folder, containing
following rules:
- Create the necessary timings (always, asap, peak, offpeak).
- Configure 3 destinations (1002, 1003 and 10 used as catch all rule).
@@ -68,11 +70,11 @@ To verify that all actions successfully performed, we use following *cgr-console
::
cgr-console 'account Tenant="cgrates.org" Account="1001"'
cgr-console 'account Tenant="cgrates.org" Account="1002"'
cgr-console 'account Tenant="cgrates.org" Account="1003"'
cgr-console 'account Tenant="cgrates.org" Account="1004"'
cgr-console 'account Tenant="cgrates.org" Account="1005"'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1002"]'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1003"]'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1004"]'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1005"]'
- Query call costs so we can see our calls will have expected costs (final cost will result as sum of *ConnectFee* and *Cost* fields):
@@ -108,7 +110,7 @@ Check that 1001 balance is properly deducted, during the call, and moreover cons
::
cgr-console 'account Tenant="cgrates.org" Account="1001"'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]'
1002 -> 1001
@@ -120,7 +122,7 @@ To check that we had debits we use again console command, this time not during t
::
cgr-console 'account Tenant="cgrates.org" Account="1002"'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1002"]'
1003 -> 1001
@@ -132,7 +134,7 @@ To check that there are no debits during or by the end of the call, but when the
::
cgr-console 'account Tenant="cgrates.org" Account="1003"'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1003"]'
1004 -> 1001
@@ -150,8 +152,8 @@ Check that 1001 balance is properly debitted, during the call, and moreover cons
::
cgr-console 'account Tenant="cgrates.org" Account="1006"'
cgr-console 'account Tenant="cgrates.org" Account="1001"'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1006"]'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]'
1007 -> 1002
@@ -163,8 +165,8 @@ Check that call can proceed even if 1007 has no units left into his own balances
::
cgr-console 'account Tenant="cgrates.org" Account="1007"'
cgr-console 'account Tenant="cgrates.org" Account="1001"'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1007"]'
cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]'
CDR Exporting
@@ -192,4 +194,4 @@ To verify this mechanism simply add some random units into one account's balance
cgr-console 'balance_set Tenant="cgrates.org" Account="1001" Direction="*out" Value=101'
tail -f /var/log/syslog -n 20
On the CDRs side we will be able to integrate CdrStats monitors as part of our Fraud Detection system (eg: the increase of average cost for 1001 and 1002 accounts will signal us abnormalities, hence we will be notified via syslog).
On the CDRs side we will be able to integrate CdrStats monitors as part of our Fraud Detection system (eg: the increase of average cost for 1001 and 1002 accounts will signal us abnormalities, hence we will be notified via syslog).

View File

@@ -150,7 +150,8 @@ func (self *CdrServer) processCdr(storedCdr *StoredCdr) (err error) {
if upData, err := LoadUserProfile(storedCdr, "ExtraFields"); err != nil {
return err
} else {
*storedCdr = upData.(StoredCdr)
cdrRcv := upData.(*StoredCdr)
*storedCdr = *cdrRcv
}
if storedCdr.ReqType == utils.META_NONE {
return nil

View File

@@ -54,6 +54,12 @@ func (rs *Responder) GetCost(arg *CallDescriptor, reply *CallCost) (err error) {
if arg.Subject == "" {
arg.Subject = arg.Account
}
if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*CallDescriptor)
*arg = *udRcv
}
if rs.Bal != nil {
r, e := rs.getCallCost(arg, "Responder.GetCost")
*reply, err = *r, e
@@ -74,6 +80,12 @@ func (rs *Responder) Debit(arg *CallDescriptor, reply *CallCost) (err error) {
if arg.Subject == "" {
arg.Subject = arg.Account
}
if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*CallDescriptor)
*arg = *udRcv
}
if rs.Bal != nil {
r, e := rs.getCallCost(arg, "Responder.Debit")
*reply, err = *r, e
@@ -92,6 +104,12 @@ func (rs *Responder) MaxDebit(arg *CallDescriptor, reply *CallCost) (err error)
if arg.Subject == "" {
arg.Subject = arg.Account
}
if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*CallDescriptor)
*arg = *udRcv
}
if rs.Bal != nil {
r, e := rs.getCallCost(arg, "Responder.MaxDebit")
*reply, err = *r, e
@@ -110,6 +128,12 @@ func (rs *Responder) RefundIncrements(arg *CallDescriptor, reply *float64) (err
if arg.Subject == "" {
arg.Subject = arg.Account
}
if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*CallDescriptor)
*arg = *udRcv
}
if rs.Bal != nil {
*reply, err = rs.callMethod(arg, "Responder.RefundIncrements")
} else {
@@ -125,6 +149,12 @@ func (rs *Responder) GetMaxSessionTime(arg *CallDescriptor, reply *float64) (err
if arg.Subject == "" {
arg.Subject = arg.Account
}
if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*CallDescriptor)
*arg = *udRcv
}
if rs.Bal != nil {
*reply, err = rs.callMethod(arg, "Responder.GetMaxSessionTime")
} else {
@@ -136,11 +166,17 @@ func (rs *Responder) GetMaxSessionTime(arg *CallDescriptor, reply *float64) (err
// Returns MaxSessionTime for an event received in SessionManager, considering DerivedCharging for it
func (rs *Responder) GetDerivedMaxSessionTime(ev *StoredCdr, reply *float64) error {
if rs.Bal != nil {
return errors.New("unsupported method on the balancer")
}
if ev.Subject == "" {
ev.Subject = ev.Account
}
if rs.Bal != nil {
return errors.New("unsupported method on the balancer")
if upData, err := LoadUserProfile(ev, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*StoredCdr)
*ev = *udRcv
}
maxCallDuration := -1.0
attrsDC := &utils.AttrDerivedChargers{Tenant: ev.GetTenant(utils.META_DEFAULT), Category: ev.GetCategory(utils.META_DEFAULT), Direction: ev.GetDirection(utils.META_DEFAULT),
@@ -205,11 +241,17 @@ func (rs *Responder) GetDerivedMaxSessionTime(ev *StoredCdr, reply *float64) err
// Used by SM to get all the prepaid CallDescriptors attached to a session
func (rs *Responder) GetSessionRuns(ev *StoredCdr, sRuns *[]*SessionRun) error {
if rs.Bal != nil {
return errors.New("Unsupported method on the balancer")
}
if ev.Subject == "" {
ev.Subject = ev.Account
}
if rs.Bal != nil {
return errors.New("Unsupported method on the balancer")
if upData, err := LoadUserProfile(ev, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*StoredCdr)
*ev = *udRcv
}
attrsDC := &utils.AttrDerivedChargers{Tenant: ev.GetTenant(utils.META_DEFAULT), Category: ev.GetCategory(utils.META_DEFAULT), Direction: ev.GetDirection(utils.META_DEFAULT),
Account: ev.GetAccount(utils.META_DEFAULT), Subject: ev.GetSubject(utils.META_DEFAULT)}
@@ -257,6 +299,12 @@ func (rs *Responder) ProcessCdr(cdr *StoredCdr, reply *string) error {
if rs.CdrSrv == nil {
return errors.New("CDR_SERVER_NOT_RUNNING")
}
if upData, err := LoadUserProfile(cdr, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*StoredCdr)
*cdr = *udRcv
}
if err := rs.CdrSrv.ProcessCdr(cdr); err != nil {
return err
}
@@ -279,6 +327,12 @@ func (rs *Responder) GetLCR(cd *CallDescriptor, reply *LCRCost) error {
if cd.Subject == "" {
cd.Subject = cd.Account
}
if upData, err := LoadUserProfile(cd, "ExtraFields"); err != nil {
return err
} else {
udRcv := upData.(*CallDescriptor)
*cd = *udRcv
}
lcrCost, err := cd.GetLCR(rs.Stats)
if err != nil {
return err

View File

@@ -360,10 +360,7 @@ func LoadUserProfile(in interface{}, extraFields string) (interface{}, error) {
if userService == nil { // no user service => no fun
return in, nil
}
m, err := utils.ToMapStringString(in)
if err != nil {
return nil, err
}
m := utils.ToMapStringString(in)
var needsUsers bool
for _, val := range m {
if val == utils.USERS {
@@ -391,12 +388,9 @@ func LoadUserProfile(in interface{}, extraFields string) (interface{}, error) {
}
// add extra fields
if extraFields != "" {
extra, err := utils.GetMapExtraFields(in, extraFields)
if err != nil {
return nil, err
}
extra := utils.GetMapExtraFields(in, extraFields)
for key, val := range extra {
if val != "" {
if val != "" && val != utils.USERS {
up.Profile[key] = val
}
}
@@ -409,7 +403,9 @@ func LoadUserProfile(in interface{}, extraFields string) (interface{}, error) {
up = ups[0]
m := up.Profile
m["Tenant"] = up.Tenant
return utils.FromMapStringString(m, in)
utils.FromMapStringString(m, in)
utils.SetMapExtraFields(in, m, extraFields)
return in, nil
}
return nil, utils.ErrNotFound
}

View File

@@ -3,6 +3,7 @@ package engine
import (
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/utils"
)
@@ -520,7 +521,7 @@ func TestUsersAddUpdateRemoveIndexes(t *testing.T) {
}
}
func TestUsersStoredCDRGetLoadUserProfile(t *testing.T) {
func TestUsersUsageRecordGetLoadUserProfile(t *testing.T) {
userService = &UserMap{
table: map[string]map[string]string{
"test:user": map[string]string{"TOR": "01", "ReqType": "1", "Direction": "*out", "Category": "c1", "Account": "dan", "Subject": "0723", "Destination": "+401", "SetupTime": "s1", "AnswerTime": "t1", "Usage": "10"},
@@ -562,13 +563,13 @@ func TestUsersStoredCDRGetLoadUserProfile(t *testing.T) {
AnswerTime: "t4",
Usage: "13",
}
*ur = out.(UsageRecord)
ur = out.(*UsageRecord)
if !reflect.DeepEqual(ur, expected) {
t.Errorf("Expected: %+v got: %+v", expected, ur)
}
}
func TestUsersStoredCDRGetLoadUserProfileExtraFields(t *testing.T) {
func TestUsersExternalCdrGetLoadUserProfileExtraFields(t *testing.T) {
userService = &UserMap{
table: map[string]map[string]string{
"test:user": map[string]string{"TOR": "01", "ReqType": "1", "Direction": "*out", "Category": "c1", "Account": "dan", "Subject": "0723", "Destination": "+401", "SetupTime": "s1", "AnswerTime": "t1", "Usage": "10"},
@@ -612,14 +613,17 @@ func TestUsersStoredCDRGetLoadUserProfileExtraFields(t *testing.T) {
SetupTime: "s4",
AnswerTime: "t4",
Usage: "13",
ExtraFields: map[string]string{
"Test": "1",
},
}
*ur = out.(ExternalCdr)
ur = out.(*ExternalCdr)
if !reflect.DeepEqual(ur, expected) {
t.Errorf("Expected: %+v got: %+v", expected, ur)
}
}
func TestUsersStoredCDRGetLoadUserProfileExtraFieldsNotFound(t *testing.T) {
func TestUsersExternalCdrGetLoadUserProfileExtraFieldsNotFound(t *testing.T) {
userService = &UserMap{
table: map[string]map[string]string{
"test:user": map[string]string{"TOR": "01", "ReqType": "1", "Direction": "*out", "Category": "c1", "Account": "dan", "Subject": "0723", "Destination": "+401", "SetupTime": "s1", "AnswerTime": "t1", "Usage": "10"},
@@ -652,3 +656,147 @@ func TestUsersStoredCDRGetLoadUserProfileExtraFieldsNotFound(t *testing.T) {
t.Error("Error detecting err in loading user profile: ", err)
}
}
func TestUsersExternalCdrGetLoadUserProfileExtraFieldsSet(t *testing.T) {
userService = &UserMap{
table: map[string]map[string]string{
"test:user": map[string]string{"TOR": "01", "ReqType": "1", "Direction": "*out", "Category": "c1", "Account": "dan", "Subject": "0723", "Destination": "+401", "SetupTime": "s1", "AnswerTime": "t1", "Usage": "10"},
":user": map[string]string{"TOR": "02", "ReqType": "2", "Direction": "*out", "Category": "c2", "Account": "ivo", "Subject": "0724", "Destination": "+402", "SetupTime": "s2", "AnswerTime": "t2", "Usage": "11"},
"test:": map[string]string{"TOR": "03", "ReqType": "3", "Direction": "*out", "Category": "c3", "Account": "elloy", "Subject": "0725", "Destination": "+403", "SetupTime": "s3", "AnswerTime": "t3", "Usage": "12"},
"test1:user1": map[string]string{"TOR": "04", "ReqType": "4", "Direction": "*out", "Category": "call", "Account": "rif", "Subject": "0726", "Destination": "+404", "SetupTime": "s4", "AnswerTime": "t4", "Usage": "13", "Test": "1", "Best": "BestValue"},
},
index: make(map[string]map[string]bool),
}
ur := &ExternalCdr{
TOR: utils.USERS,
ReqType: utils.USERS,
Direction: "*out",
Tenant: "",
Category: "call",
Account: utils.USERS,
Subject: utils.USERS,
Destination: utils.USERS,
SetupTime: utils.USERS,
AnswerTime: utils.USERS,
Usage: "13",
ExtraFields: map[string]string{
"Test": "1",
"Best": utils.USERS,
},
}
out, err := LoadUserProfile(ur, "ExtraFields")
if err != nil {
t.Error("Error loading user profile: ", err)
}
expected := &ExternalCdr{
TOR: "04",
ReqType: "4",
Direction: "*out",
Tenant: "",
Category: "call",
Account: "rif",
Subject: "0726",
Destination: "+404",
SetupTime: "s4",
AnswerTime: "t4",
Usage: "13",
ExtraFields: map[string]string{
"Test": "1",
"Best": "BestValue",
},
}
ur = out.(*ExternalCdr)
if !reflect.DeepEqual(ur, expected) {
t.Errorf("Expected: %+v got: %+v", expected, ur)
}
}
func TestUsersCallDescLoadUserProfile(t *testing.T) {
userService = &UserMap{
table: map[string]map[string]string{
"cgrates.org:dan": map[string]string{"ReqType": "*prepaid", "Category": "call1", "Account": "dan", "Subject": "dan", "Cli": "+4986517174963"},
"cgrates.org:danvoice": map[string]string{"TOR": "*voice", "ReqType": "*prepaid", "Category": "call1", "Account": "dan", "Subject": "0723"},
"cgrates:rif": map[string]string{"ReqType": "*postpaid", "Direction": "*out", "Category": "call", "Account": "rif", "Subject": "0726"},
},
index: make(map[string]map[string]bool),
}
startTime := time.Now()
cd := &CallDescriptor{
TOR: "*sms",
Tenant: utils.USERS,
Category: utils.USERS,
Subject: utils.USERS,
Account: utils.USERS,
Destination: "+4986517174963",
TimeStart: startTime,
TimeEnd: startTime.Add(time.Duration(1) * time.Minute),
ExtraFields: map[string]string{"Cli": "+4986517174963"},
}
expected := &CallDescriptor{
TOR: "*sms",
Tenant: "cgrates.org",
Category: "call1",
Account: "dan",
Subject: "dan",
Destination: "+4986517174963",
TimeStart: startTime,
TimeEnd: startTime.Add(time.Duration(1) * time.Minute),
ExtraFields: map[string]string{"Cli": "+4986517174963"},
}
out, err := LoadUserProfile(cd, "ExtraFields")
if err != nil {
t.Error("Error loading user profile: ", err)
}
cdRcv := out.(*CallDescriptor)
if !reflect.DeepEqual(expected, cdRcv) {
t.Errorf("Expected: %+v got: %+v", expected, cdRcv)
}
}
func TestUsersStoredCdrLoadUserProfile(t *testing.T) {
userService = &UserMap{
table: map[string]map[string]string{
"cgrates.org:dan": map[string]string{"ReqType": "*prepaid", "Category": "call1", "Account": "dan", "Subject": "dan", "Cli": "+4986517174963"},
"cgrates.org:danvoice": map[string]string{"TOR": "*voice", "ReqType": "*prepaid", "Category": "call1", "Account": "dan", "Subject": "0723"},
"cgrates:rif": map[string]string{"ReqType": "*postpaid", "Direction": "*out", "Category": "call", "Account": "rif", "Subject": "0726"},
},
index: make(map[string]map[string]bool),
}
startTime := time.Now()
cdr := &StoredCdr{
TOR: "*sms",
ReqType: utils.USERS,
Tenant: utils.USERS,
Category: utils.USERS,
Account: utils.USERS,
Subject: utils.USERS,
Destination: "+4986517174963",
SetupTime: startTime,
AnswerTime: startTime,
Usage: time.Duration(1) * time.Minute,
ExtraFields: map[string]string{"Cli": "+4986517174963"},
}
expected := &StoredCdr{
TOR: "*sms",
ReqType: "*prepaid",
Tenant: "cgrates.org",
Category: "call1",
Account: "dan",
Subject: "dan",
Destination: "+4986517174963",
SetupTime: startTime,
AnswerTime: startTime,
Usage: time.Duration(1) * time.Minute,
ExtraFields: map[string]string{"Cli": "+4986517174963"},
}
out, err := LoadUserProfile(cdr, "ExtraFields")
if err != nil {
t.Error("Error loading user profile: ", err)
}
cdRcv := out.(*StoredCdr)
if !reflect.DeepEqual(expected, cdRcv) {
t.Errorf("Expected: %+v got: %+v", expected, cdRcv)
}
}

View File

@@ -78,7 +78,7 @@ func NonemptyStructFields(s interface{}) map[string]interface{} {
}*/
// Converts a struct to map[string]interface{}
func ToMapMapStringInterface(in interface{}) (map[string]interface{}, error) {
func ToMapMapStringInterface(in interface{}) map[string]interface{} {
out := make(map[string]interface{})
v := reflect.ValueOf(in)
@@ -89,11 +89,11 @@ func ToMapMapStringInterface(in interface{}) (map[string]interface{}, error) {
for i := 0; i < v.NumField(); i++ {
out[typ.Field(i).Name] = v.Field(i).Interface()
}
return out, nil
return out
}
// Converts a struct to map[string]string
func ToMapStringString(in interface{}) (map[string]string, error) {
func ToMapStringString(in interface{}) map[string]string {
out := make(map[string]string)
v := reflect.ValueOf(in)
@@ -110,11 +110,10 @@ func ToMapStringString(in interface{}) (map[string]string, error) {
out[typField.Name] = field.String()
}
}
return out, nil
return out
}
// Converts a struct to map[string]string
func GetMapExtraFields(in interface{}, extraFields string) (map[string]string, error) {
func GetMapExtraFields(in interface{}, extraFields string) map[string]string {
out := make(map[string]string)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
@@ -128,21 +127,38 @@ func GetMapExtraFields(in interface{}, extraFields string) (map[string]string, e
out[key.String()] = field.MapIndex(key).String()
}
}
return out, nil
return out
}
func FromMapStringString(m map[string]string, in interface{}) (interface{}, error) {
func SetMapExtraFields(in interface{}, values map[string]string, extraFields string) {
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
in = v.Interface()
}
st := reflect.TypeOf(in)
elem := reflect.New(st).Elem()
for fieldName, fieldValue := range m {
field := elem.FieldByName(fieldName)
if field.IsValid() {
efField := v.FieldByName(extraFields)
if efField.Kind() == reflect.Map {
keys := efField.MapKeys()
for _, key := range keys {
if efField.MapIndex(key).String() != "" {
if val, found := values[key.String()]; found {
efField.SetMapIndex(key, reflect.ValueOf(val))
}
}
}
}
return
}
func FromMapStringString(m map[string]string, in interface{}) {
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
in = v.Interface()
}
for fieldName, fieldValue := range m {
field := v.FieldByName(fieldName)
if field.IsValid() {
if field.Kind() == reflect.String {
if v.FieldByName(fieldName).String() != "" {
field.SetString(fieldValue)
@@ -150,7 +166,7 @@ func FromMapStringString(m map[string]string, in interface{}) (interface{}, erro
}
}
}
return elem.Interface(), nil
return
}
// Update struct with map fields, returns not matching map keys, s is a struct to be updated

View File

@@ -18,16 +18,16 @@ func TestStructMapStruct(t *testing.T) {
Address: "3",
Other: "",
}
m, err := ToMapStringString(ts)
if err != nil {
t.Error("Error converting to map: ", err)
nts := &TestStruct{
Name: "1",
Surname: "2",
Address: "3",
Other: "",
}
out, err := FromMapStringString(m, ts)
if err != nil {
t.Error("Error converting to struct: ", err)
}
nts := out.(TestStruct)
if !reflect.DeepEqual(ts, &nts) {
m := ToMapStringString(ts)
FromMapStringString(m, ts)
if !reflect.DeepEqual(ts, nts) {
t.Log(m)
t.Errorf("Expected: %+v got: %+v", ts, nts)
}
@@ -46,17 +46,17 @@ func TestMapStructAddStructs(t *testing.T) {
Address: "3",
Other: "",
}
m, err := ToMapStringString(ts)
if err != nil {
t.Error("Error converting to map: ", err)
nts := &TestStruct{
Name: "1",
Surname: "2",
Address: "3",
Other: "",
}
m := ToMapStringString(ts)
m["Test"] = "4"
out, err := FromMapStringString(m, ts)
if err != nil {
t.Error("Error converting to struct: ", err)
}
nts := out.(TestStruct)
if !reflect.DeepEqual(ts, &nts) {
FromMapStringString(m, ts)
if !reflect.DeepEqual(ts, nts) {
t.Log(m)
t.Errorf("Expected: %+v got: %+v", ts, nts)
}
@@ -78,10 +78,7 @@ func TestStructExtraFields(t *testing.T) {
"k3": "v3",
},
}
efMap, err := GetMapExtraFields(ts, "ExtraFields")
if err != nil {
t.Error("Error getting extra fields: ", err)
}
efMap := GetMapExtraFields(ts, "ExtraFields")
if !reflect.DeepEqual(efMap, ts.ExtraFields) {
t.Errorf("expected: %v got: %v", ts.ExtraFields, efMap)