diff --git a/agents/dmtagent.go b/agents/dmtagent.go index 0a61fb2c8..35c43806e 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -92,14 +92,21 @@ func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestPro err = errCdr } } - if err != nil { - return nil, err - } - cca := NewCCAFromCCR(ccr) + cca := NewBareCCAFromCCR(ccr) cca.OriginHost = self.cgrCfg.DiameterAgentCfg().OriginHost cca.OriginRealm = self.cgrCfg.DiameterAgentCfg().OriginRealm + if err != nil { + cca.ResultCode = DiameterRatingFailed + utils.Logger.Err(fmt.Sprintf(" Processing message: %+v, error: %s", ccr.diamMessage, err)) + return cca, nil + } cca.ResultCode = diam.Success cca.GrantedServiceUnit.CCTime = int(maxUsage) + if err := cca.SetProcessorAVPs(reqProcessor); err != nil { + cca.ResultCode = DiameterRatingFailed + utils.Logger.Err(fmt.Sprintf(" Processing message: %+v, error: %s", ccr.diamMessage, err)) + return cca, nil + } return cca, nil } @@ -124,11 +131,8 @@ func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { utils.Logger.Err(fmt.Sprintf(" No request processor enabled for CCR: %+v, ignoring request", ccr)) return } - if dmtA, err := cca.AsBareDiameterMessage(); err != nil { - utils.Logger.Err(fmt.Sprintf(" Failed to convert cca as diameter message, error: %s", err.Error())) - return - } else if _, err := dmtA.WriteTo(c); err != nil { - utils.Logger.Err(fmt.Sprintf(" Failed to write message to %s: %s\n%s\n", c.RemoteAddr(), err, dmtA)) + if _, err := cca.AsDiameterMessage().WriteTo(c); err != nil { + utils.Logger.Err(fmt.Sprintf(" Failed to write message to %s: %s\n%s\n", c.RemoteAddr(), err, cca.AsDiameterMessage())) return } } diff --git a/agents/libdmt.go b/agents/libdmt.go index 778de367c..a652c9c6a 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -48,8 +48,9 @@ func init() { } const ( - META_CCR_USAGE = "*ccr_usage" - DIAMETER_CCR = "DIAMETER_CCR" + META_CCR_USAGE = "*ccr_usage" + DIAMETER_CCR = "DIAMETER_CCR" + DiameterRatingFailed = 5031 ) func loadDictionaries(dictsDir, componentId string) error { @@ -168,7 +169,7 @@ func avpValAsString(a *diam.AVP) string { } // Handler for meta functions -func metaHandler(m *diam.Message, tag, arg string, debitInterval time.Duration) (string, error) { +func metaHandler(m *diam.Message, tag, arg string, dur time.Duration) (string, error) { switch tag { case META_CCR_USAGE: ccReqTypeAvp, err := m.FindAVP("CC-Request-Type", dict.UndefinedVendorID) @@ -198,7 +199,7 @@ func metaHandler(m *diam.Message, tag, arg string, debitInterval time.Duration) usage := usageFromCCR(int(ccReqTypeAvp.Data.(datatype.Enumerated)), int(ccReqNrAvp.Data.(datatype.Enumerated)), int(reqUnitAVPs[0].Data.(datatype.Unsigned32)), - int(usedUnitAVPs[0].Data.(datatype.Unsigned32)), debitInterval) + int(usedUnitAVPs[0].Data.(datatype.Unsigned32)), dur) return strconv.FormatFloat(usage.Seconds(), 'f', -1, 64), nil } return "", nil @@ -304,7 +305,8 @@ func fieldOutVal(m *diam.Message, cfgFld *config.CfgCdrField, debitInterval time } // messageAddAVPsWithPath will dynamically add AVPs into the message -func messageAddAVPsWithPath(m *diam.Message, path []interface{}, avpValByte []byte) error { +// append: append to the message, on false overwrite if AVP is single or add to group if AVP is Grouped +func messageSetAVPsWithPath(m *diam.Message, path []interface{}, avpValByte []byte, appnd bool) error { if len(path) == 0 { return errors.New("Empty path as AVP filter") } @@ -322,10 +324,11 @@ func messageAddAVPsWithPath(m *diam.Message, path []interface{}, avpValByte []by return errors.New("Last AVP in path needs not to be GroupedAVP") } var msgAVP *diam.AVP // Keep a reference here towards last AVP - for i := len(path) - 1; i >= 0; i-- { + lastAVPIdx := len(path) - 1 + for i := lastAVPIdx; i >= 0; i-- { var typeVal datatype.Type var err error - if msgAVP == nil { + if i == lastAVPIdx { typeVal, err = datatype.Decode(dictAVPs[i].Data.Type, avpValByte) if err != nil { return err @@ -334,7 +337,26 @@ func messageAddAVPsWithPath(m *diam.Message, path []interface{}, avpValByte []by typeVal = &diam.GroupedAVP{ AVP: []*diam.AVP{msgAVP}} } - msgAVP = diam.NewAVP(dictAVPs[i].Code, avp.Mbit, dictAVPs[i].VendorID, typeVal) // FixMe: maybe Mbit with dictionary one + newMsgAVP := diam.NewAVP(dictAVPs[i].Code, avp.Mbit, dictAVPs[i].VendorID, typeVal) // FixMe: maybe Mbit with dictionary one + if i == lastAVPIdx-1 && !appnd { // last AVP needs to be appended in group + avps, _ := m.FindAVPsWithPath(path[:lastAVPIdx], dict.UndefinedVendorID) + if len(avps) != 0 { // Group AVP already in the message + prevGrpData := avps[0].Data.(*diam.GroupedAVP) + prevGrpData.AVP = append(prevGrpData.AVP, msgAVP) + m.Header.MessageLength += uint32(msgAVP.Len()) + return nil + } + } + msgAVP = newMsgAVP + } + if !appnd { // Not group AVP, replace the previous set one with this one + avps, _ := m.FindAVPsWithPath(path, dict.UndefinedVendorID) + if len(avps) != 0 { // Group AVP already in the message + m.Header.MessageLength -= uint32(avps[0].Len()) // decrease message length since we overwrite + *avps[0] = *msgAVP + m.Header.MessageLength += uint32(msgAVP.Len()) + return nil + } } m.AVP = append(m.AVP, msgAVP) m.Header.MessageLength += uint32(msgAVP.Len()) @@ -353,6 +375,7 @@ func NewCCRFromDiameterMessage(m *diam.Message, debitInterval time.Duration) (*C } // CallControl Request +// FixMe: strip it down to mandatory bare structure format by RFC 4006 type CCR struct { SessionId string `avp:"Session-Id"` OriginHost string `avp:"Origin-Host"` @@ -395,36 +418,31 @@ type CCR struct { debitInterval time.Duration // Configured debit interval } +// AsBareDiameterMessage converts CCR into a bare DiameterMessage +// Compatible with the required fields of CCA +func (self *CCR) AsBareDiameterMessage() *diam.Message { + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String(self.SessionId)) + m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity(self.OriginHost)) + m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity(self.OriginRealm)) + m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(self.AuthApplicationId)) + m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(self.CCRequestType)) + m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Enumerated(self.CCRequestNumber)) + return m +} + // Used when sending from client to agent func (self *CCR) AsDiameterMessage() (*diam.Message, error) { - m := diam.NewRequest(diam.CreditControl, 4, nil) - if _, err := m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String(self.SessionId)); err != nil { - return nil, err - } - if _, err := m.NewAVP("Origin-Host", avp.Mbit, 0, datatype.DiameterIdentity(self.OriginHost)); err != nil { - return nil, err - } - if _, err := m.NewAVP("Origin-Realm", avp.Mbit, 0, datatype.DiameterIdentity(self.OriginRealm)); err != nil { - return nil, err - } + m := self.AsBareDiameterMessage() if _, err := m.NewAVP("Destination-Host", avp.Mbit, 0, datatype.DiameterIdentity(self.DestinationHost)); err != nil { return nil, err } if _, err := m.NewAVP("Destination-Realm", avp.Mbit, 0, datatype.DiameterIdentity(self.DestinationRealm)); err != nil { return nil, err } - if _, err := m.NewAVP("Auth-Application-Id", avp.Mbit, 0, datatype.Unsigned32(self.AuthApplicationId)); err != nil { - return nil, err - } if _, err := m.NewAVP("Service-Context-Id", avp.Mbit, 0, datatype.UTF8String(self.ServiceContextId)); err != nil { return nil, err } - if _, err := m.NewAVP("CC-Request-Type", avp.Mbit, 0, datatype.Enumerated(self.CCRequestType)); err != nil { - return nil, err - } - if _, err := m.NewAVP("CC-Request-Number", avp.Mbit, 0, datatype.Enumerated(self.CCRequestNumber)); err != nil { - return nil, err - } if _, err := m.NewAVP("Event-Timestamp", avp.Mbit, 0, datatype.Time(self.EventTimestamp)); err != nil { return nil, err } @@ -483,20 +501,23 @@ func (self *CCR) AsSMGenericEvent(cfgFlds []*config.CfgCdrField) (sessionmanager if err != nil { return nil, err } - if _, hasKey := outMap[cfgFld.FieldId]; !hasKey { - outMap[cfgFld.FieldId] = fmtOut - } else { // If already there, postpend + if _, hasKey := outMap[cfgFld.FieldId]; hasKey && cfgFld.Append { outMap[cfgFld.FieldId] += fmtOut + } else { + outMap[cfgFld.FieldId] = fmtOut + } } return sessionmanager.SMGenericEvent(utils.ConvertMapValStrIf(outMap)), nil } -func NewCCAFromCCR(ccr *CCR) *CCA { - return &CCA{SessionId: ccr.SessionId, AuthApplicationId: ccr.AuthApplicationId, CCRequestType: ccr.CCRequestType, CCRequestNumber: ccr.CCRequestNumber, +func NewBareCCAFromCCR(ccr *CCR) *CCA { + cca := &CCA{SessionId: ccr.SessionId, AuthApplicationId: ccr.AuthApplicationId, CCRequestType: ccr.CCRequestType, CCRequestNumber: ccr.CCRequestNumber, diamMessage: diam.NewMessage(ccr.diamMessage.Header.CommandCode, ccr.diamMessage.Header.CommandFlags&^diam.RequestFlag, ccr.diamMessage.Header.ApplicationID, - ccr.diamMessage.Header.HopByHopID, ccr.diamMessage.Header.EndToEndID, ccr.diamMessage.Dictionary()), + ccr.diamMessage.Header.HopByHopID, ccr.diamMessage.Header.EndToEndID, ccr.diamMessage.Dictionary()), ccrMessage: ccr.diamMessage, debitInterval: ccr.debitInterval, } + cca.diamMessage = cca.AsBareDiameterMessage() // Add the required fields to the diameterMessage + return cca } // Call Control Answer, bare structure so we can dynamically manage adding it's fields @@ -511,42 +532,40 @@ type CCA struct { GrantedServiceUnit struct { CCTime int `avp:"CC-Time"` } `avp:"Granted-Service-Unit"` - diamMessage *diam.Message + ccrMessage *diam.Message + diamMessage *diam.Message + debitInterval time.Duration } -// Converts itself into DiameterMessage -func (self *CCA) AsBareDiameterMessage() (*diam.Message, error) { - if _, err := self.diamMessage.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String(self.SessionId)); err != nil { - return nil, err - } - if _, err := self.diamMessage.NewAVP("Origin-Host", avp.Mbit, 0, datatype.DiameterIdentity(self.OriginHost)); err != nil { - return nil, err - } - if _, err := self.diamMessage.NewAVP("Origin-Realm", avp.Mbit, 0, datatype.DiameterIdentity(self.OriginRealm)); err != nil { - return nil, err - } - if _, err := self.diamMessage.NewAVP("Auth-Application-Id", avp.Mbit, 0, datatype.Unsigned32(self.AuthApplicationId)); err != nil { - return nil, err - } - if _, err := self.diamMessage.NewAVP("CC-Request-Type", avp.Mbit, 0, datatype.Enumerated(self.CCRequestType)); err != nil { - return nil, err - } - if _, err := self.diamMessage.NewAVP("CC-Request-Number", avp.Mbit, 0, datatype.Enumerated(self.CCRequestNumber)); err != nil { - return nil, err - } - if _, err := self.diamMessage.NewAVP(avp.ResultCode, avp.Mbit, 0, datatype.Unsigned32(self.ResultCode)); err != nil { - return nil, err - } - /* - ccTimeAvp, err := self.diamMessage.Dictionary().FindAVP(self.diamMessage.Header.ApplicationID, "CC-Time") - if err != nil { - return nil, err - } - if _, err := self.diamMessage.NewAVP("Granted-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(ccTimeAvp.Code, avp.Mbit, 0, datatype.Unsigned32(self.GrantedServiceUnit.CCTime))}}); err != nil { - return nil, err - } - */ - return self.diamMessage, nil +// AsBareDiameterMessage converts CCA into a bare DiameterMessage +func (self *CCA) AsBareDiameterMessage() *diam.Message { + var m diam.Message + utils.Clone(self.diamMessage, &m) + m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String(self.SessionId)) + m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity(self.OriginHost)) + m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity(self.OriginRealm)) + m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(self.AuthApplicationId)) + m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(self.CCRequestType)) + m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Enumerated(self.CCRequestNumber)) + m.NewAVP(avp.ResultCode, avp.Mbit, 0, datatype.Unsigned32(self.ResultCode)) + return &m +} + +// AsDiameterMessage returns the diameter.Message which can be later written on network +func (self *CCA) AsDiameterMessage() *diam.Message { + return self.diamMessage +} + +// SetProcessorAVPs will add AVPs to self.diameterMessage based on template defined in processor.CCAFields +func (self *CCA) SetProcessorAVPs(reqProcessor *config.DARequestProcessor) error { + for _, cfgFld := range reqProcessor.CCAFields { + fmtOut, err := fieldOutVal(self.ccrMessage, cfgFld, self.debitInterval) + if err != nil { + return err + } + if err := messageSetAVPsWithPath(self.diamMessage, splitIntoInterface(cfgFld.Value.Id(), utils.HIERARCHY_SEP), []byte(fmtOut), cfgFld.Append); err != nil { + return err + } + } + return nil } diff --git a/agents/libdmt_test.go b/agents/libdmt_test.go index 68fdb608e..d1798438d 100644 --- a/agents/libdmt_test.go +++ b/agents/libdmt_test.go @@ -19,6 +19,7 @@ along with this program. If not, see package agents import ( + "bytes" "reflect" "testing" "time" @@ -137,16 +138,113 @@ func TestFieldOutVal(t *testing.T) { } } -func TestMessageAddAVPsWithPath(t *testing.T) { +func TestMessageSetAVPsWithPath(t *testing.T) { eMessage := diam.NewRequest(diam.CreditControl, 4, nil) - eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data - }}) + eMessage.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002")) m := diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) - if err := messageAddAVPsWithPath(m, []interface{}{"Subscription-Id", "Subscription-Id-Data"}, []byte("33708000003")); err != nil { + if err := messageSetAVPsWithPath(m, []interface{}{"Session-Id", "Unknown"}, []byte("simuhuawei;1449573472;00002"), false); err == nil || err.Error() != "Could not find AVP Unknown" { + t.Error(err) + } + if err := messageSetAVPsWithPath(m, []interface{}{"Session-Id"}, []byte("simuhuawei;1449573472;00002"), false); err != nil { t.Error(err) } else if !reflect.DeepEqual(eMessage, m) { t.Errorf("Expecting: %+v, received: %+v", eMessage, m) } + // test append + eMessage.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00003")) + if err := messageSetAVPsWithPath(m, []interface{}{"Session-Id"}, []byte("simuhuawei;1449573472;00003"), true); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eMessage, m) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + // test overwrite + eMessage = diam.NewRequest(diam.CreditControl, 4, nil) + eMessage.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002")) + m = diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) + if err := messageSetAVPsWithPath(m, []interface{}{"Session-Id"}, []byte("simuhuawei;1449573472;00001"), false); err != nil { + t.Error(err) + } + if err := messageSetAVPsWithPath(m, []interface{}{"Session-Id"}, []byte("simuhuawei;1449573472;00002"), false); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eMessage, m) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + eMessage = diam.NewRequest(diam.CreditControl, 4, nil) + eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data + }}) + m = diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) + if err := messageSetAVPsWithPath(m, []interface{}{"Subscription-Id", "Subscription-Id-Data"}, []byte("33708000003"), false); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eMessage, m) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + // diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(subscriptionId.SubscriptionIdType)), // Subscription-Id-Type + // test append + eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Data + }}) + if err := messageSetAVPsWithPath(m, []interface{}{"Subscription-Id", "Subscription-Id-Type"}, []byte("0"), true); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eMessage, m) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + // test group append + eMessage = diam.NewRequest(diam.CreditControl, 4, nil) + eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Data + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data + }}) + eMsgSrl, _ := eMessage.Serialize() + m = diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) + if err := messageSetAVPsWithPath(m, []interface{}{"Subscription-Id", "Subscription-Id-Type"}, []byte("0"), false); err != nil { + t.Error(err) + } + if err := messageSetAVPsWithPath(m, []interface{}{"Subscription-Id", "Subscription-Id-Data"}, []byte("33708000003"), false); err != nil { + t.Error(err) + } else { + mSrl, _ := m.Serialize() + if !bytes.Equal(eMsgSrl, mSrl) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + } +} + +func TestCCASetProcessorAVPs(t *testing.T) { + ccr := &CCR{ // Bare information, just the one needed for answer + SessionId: "routinga;1442095190;1476802709", + AuthApplicationId: 4, + CCRequestType: 1, + CCRequestNumber: 0, + } + ccr.diamMessage = ccr.AsBareDiameterMessage() + ccr.diamMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data + }}) + ccr.debitInterval = time.Duration(300) * time.Second + cca := NewBareCCAFromCCR(ccr) + reqProcessor := &config.DARequestProcessor{Id: "UNIT_TEST", // Set template for tests + CCAFields: []*config.CfgCdrField{ + &config.CfgCdrField{Tag: "Subscription-Id/Subscription-Id-Type", Type: utils.META_COMPOSED, + Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Type", utils.INFIELD_SEP), Mandatory: true}, + &config.CfgCdrField{Tag: "Subscription-Id/Subscription-Id-Data", Type: utils.META_COMPOSED, + Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), Mandatory: true}, + }, + } + eMessage := cca.AsDiameterMessage() + eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data + }}) + if err := cca.SetProcessorAVPs(reqProcessor); err != nil { + t.Error(err) + } else if ccaMsg := cca.AsDiameterMessage(); !reflect.DeepEqual(eMessage, ccaMsg) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, ccaMsg) + } } diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index 2b69a62ba..c94a788ca 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -216,6 +216,19 @@ func (self *ApierV1) SetAccount(attr utils.AttrSetAccount, reply *string) error } ap.AccountIDs[accID] = struct{}{} schedulerReloadNeeded = true + // create tasks + for _, at := range ap.ActionTimings { + if at.IsASAP() { + t := &engine.Task{ + Uuid: utils.GenUUID(), + AccountID: accID, + ActionsID: at.ActionsID, + } + if err = self.RatingDb.PushTask(t); err != nil { + return 0, err + } + } + } if err := self.RatingDb.SetActionPlan(attr.ActionPlanId, ap); err != nil { return 0, err } diff --git a/config/cfgcdrfield.go b/config/cfgcdrfield.go index ddaac583a..06b02e2d5 100644 --- a/config/cfgcdrfield.go +++ b/config/cfgcdrfield.go @@ -42,6 +42,9 @@ func NewCfgCdrFieldFromCdrFieldJsonCfg(jsnCfgFld *CdrFieldJsonCfg) (*CfgCdrField return nil, err } } + if jsnCfgFld.Append != nil { + cfgFld.Append = *jsnCfgFld.Append + } if jsnCfgFld.Field_filter != nil { if cfgFld.FieldFilter, err = utils.ParseRSRFields(*jsnCfgFld.Field_filter, utils.INFIELD_SEP); err != nil { return nil, err @@ -71,6 +74,7 @@ type CfgCdrField struct { FieldId string // Field identifier HandlerId string Value utils.RSRFields + Append bool FieldFilter utils.RSRFields Width int Strip string diff --git a/config/libconfig_json.go b/config/libconfig_json.go index 40534951e..26d09123b 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -111,6 +111,7 @@ type CdrFieldJsonCfg struct { Field_id *string Handler_id *string Value *string + Append *bool Width *int Strip *string Padding *string diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index f687264c4..db6e1d1c7 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -1087,7 +1087,7 @@ func TestTutLocalSetAccount(t *testing.T) { return } var reply string - attrs := &utils.AttrSetAccount{Tenant: "cgrates.org", Account: "tutacnt1", ActionPlanId: "PACKAGE_10", ActionTriggersId: "STANDARD_TRIGGERS"} + attrs := &utils.AttrSetAccount{Tenant: "cgrates.org", Account: "tutacnt1", ActionPlanId: "PACKAGE_10", ActionTriggersId: "STANDARD_TRIGGERS", ReloadScheduler: true} if err := tutLocalRpc.Call("ApierV2.SetAccount", attrs, &reply); err != nil { t.Error("Got error on ApierV2.SetAccount: ", err.Error()) } else if reply != "OK" { @@ -1100,7 +1100,7 @@ func TestTutLocalSetAccount(t *testing.T) { Offset int // Set the item offset Limit int // Limit number of items retrieved } - time.Sleep(100*time.Millisecond + time.Duration(*waitRater)*time.Millisecond) // Give time for scheduler to execute topups + time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups var acnts []*engine.Account if err := tutLocalRpc.Call("ApierV2.GetAccounts", utils.AttrGetAccounts{Tenant: attrs.Tenant, AccountIds: []string{attrs.Account}}, &acnts); err != nil { t.Error(err) @@ -1125,7 +1125,7 @@ func TestTutLocalSetAccount(t *testing.T) { t.Error("Disabled should not be set") } } - attrs = &utils.AttrSetAccount{Tenant: "cgrates.org", Account: "tutacnt1", AllowNegative: utils.BoolPointer(true), Disabled: utils.BoolPointer(true)} + attrs = &utils.AttrSetAccount{Tenant: "cgrates.org", Account: "tutacnt1", AllowNegative: utils.BoolPointer(true), Disabled: utils.BoolPointer(true), ReloadScheduler: true} if err := tutLocalRpc.Call("ApierV2.SetAccount", attrs, &reply); err != nil { t.Error("Got error on ApierV2.SetAccount: ", err.Error()) } else if reply != "OK" {