diff --git a/agents/libagents.go b/agents/libagents.go index 80916f7de..ccd04a187 100644 --- a/agents/libagents.go +++ b/agents/libagents.go @@ -68,6 +68,8 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, fmt.Sprintf("<%s> LOG, processorID: %s, %s message: %s", agentName, reqProcessor.ID, strings.ToLower(agentName[:len(agentName)-5]), agReq.Request.String())) } + + replyState := utils.OK switch reqType { default: return false, fmt.Errorf("unknown request type: <%s>", reqType) @@ -96,6 +98,9 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, rply := new(sessions.V1AuthorizeReply) err = connMgr.Call(ctx, sessionsConns, utils.SessionSv1AuthorizeEvent, authArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateAuthorize + } rply.SetMaxUsageNeeded(authArgs.GetMaxUsage) agReq.setCGRReply(rply, err) case utils.MetaInitiate: @@ -112,6 +117,9 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, rply := new(sessions.V1InitSessionReply) err = connMgr.Call(ctx, sessionsConns, utils.SessionSv1InitiateSession, initArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateInitiate + } rply.SetMaxUsageNeeded(initArgs.InitSession) agReq.setCGRReply(rply, err) case utils.MetaUpdate: @@ -128,6 +136,9 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, rply.SetMaxUsageNeeded(updateArgs.UpdateSession) err = connMgr.Call(ctx, sessionsConns, utils.SessionSv1UpdateSession, updateArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateUpdate + } agReq.setCGRReply(rply, err) case utils.MetaTerminate: terminateArgs := sessions.NewV1TerminateSessionArgs( @@ -141,6 +152,9 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, var rply string err = connMgr.Call(ctx, sessionsConns, utils.SessionSv1TerminateSession, terminateArgs, &rply) + if err != nil { + replyState = utils.ErrReplyStateTerminate + } agReq.setCGRReply(nil, err) case utils.MetaMessage: msgArgs := sessions.NewV1ProcessMessageArgs( @@ -162,6 +176,9 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, rply := new(sessions.V1ProcessMessageReply) err = connMgr.Call(ctx, sessionsConns, utils.SessionSv1ProcessMessage, msgArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateMessage + } if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) { cgrEv.Event[utils.Usage] = 0 // avoid further debits } else if msgArgs.Debit { @@ -178,6 +195,9 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, rply := new(sessions.V1ProcessEventReply) err = connMgr.Call(ctx, sessionsConns, utils.SessionSv1ProcessEvent, evArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateEvent + } if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) { cgrEv.Event[utils.Usage] = 0 // avoid further debits } else if needsMaxUsage(reqProcessor.Flags[utils.MetaRALs]) { @@ -186,6 +206,7 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, agReq.setCGRReply(rply, err) case utils.MetaCDRs: // allow CDR processing } + // separate request so we can capture the Terminate/Event also here if reqProcessor.Flags.GetBool(utils.MetaCDRs) && !reqProcessor.Flags.Has(utils.MetaDryRun) { @@ -193,6 +214,11 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, if err = connMgr.Call(ctx, sessionsConns, utils.SessionSv1ProcessCDR, cgrEv, &rplyCDRs); err != nil { agReq.CGRReply.Map[utils.Error] = utils.NewLeafNode(err.Error()) + if replyState == utils.OK { + replyState = utils.ErrReplyStateCDRs + } else { + replyState += ";" + utils.ErrReplyStateCDRs + } } } if err = agReq.SetFields(reqProcessor.ReplyFields); err != nil { @@ -235,6 +261,7 @@ func processRequest(ctx *context.Context, reqProcessor *config.RequestProcessor, // asynchronously. ev := cgrEv.Clone() + ev.Event[utils.ReplyState] = replyState ev.Event[utils.StartTime] = startTime ev.Event[utils.EndTime] = endTime ev.Event[utils.ProcessingTime] = endTime.Sub(startTime) diff --git a/agents/radagent.go b/agents/radagent.go index 0a6fb7b7b..22eefad43 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -339,6 +339,8 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R fmt.Sprintf("<%s> LOG, processorID: %s, radius message: %s", utils.RadiusAgent, reqProcessor.ID, agReq.Request.String())) } + + replyState := utils.OK switch reqType { default: return false, fmt.Errorf("unknown request type: <%s>", reqType) @@ -366,6 +368,9 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R rply := new(sessions.V1AuthorizeReply) err = ra.connMgr.Call(ra.ctx, ra.cgrCfg.RadiusAgentCfg().SessionSConns, utils.SessionSv1AuthorizeEvent, authArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateAuthorize + } rply.SetMaxUsageNeeded(authArgs.GetMaxUsage) agReq.setCGRReply(rply, err) case utils.MetaInitiate: @@ -382,6 +387,9 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R rply := new(sessions.V1InitSessionReply) err = ra.connMgr.Call(ra.ctx, ra.cgrCfg.RadiusAgentCfg().SessionSConns, utils.SessionSv1InitiateSession, initArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateInitiate + } rply.SetMaxUsageNeeded(initArgs.InitSession) agReq.setCGRReply(rply, err) case utils.MetaUpdate: @@ -397,6 +405,9 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R rply := new(sessions.V1UpdateSessionReply) err = ra.connMgr.Call(ra.ctx, ra.cgrCfg.RadiusAgentCfg().SessionSConns, utils.SessionSv1UpdateSession, updateArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateUpdate + } rply.SetMaxUsageNeeded(updateArgs.UpdateSession) agReq.setCGRReply(rply, err) case utils.MetaTerminate: @@ -411,6 +422,9 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R var rply string err = ra.connMgr.Call(ra.ctx, ra.cgrCfg.RadiusAgentCfg().SessionSConns, utils.SessionSv1TerminateSession, terminateArgs, &rply) + if err != nil { + replyState = utils.ErrReplyStateTerminate + } agReq.setCGRReply(nil, err) case utils.MetaMessage: evArgs := sessions.NewV1ProcessMessageArgs( @@ -431,6 +445,9 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R rply := new(sessions.V1ProcessMessageReply) err = ra.connMgr.Call(ra.ctx, ra.cgrCfg.RadiusAgentCfg().SessionSConns, utils.SessionSv1ProcessMessage, evArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateMessage + } if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) { cgrEv.Event[utils.Usage] = 0 // avoid further debits } else if evArgs.Debit { @@ -447,6 +464,9 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R rply := new(sessions.V1ProcessEventReply) err = ra.connMgr.Call(ra.ctx, ra.cgrCfg.RadiusAgentCfg().SessionSConns, utils.SessionSv1ProcessEvent, evArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateEvent + } if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) { cgrEv.Event[utils.Usage] = 0 // avoid further debits } else if needsMaxUsage(reqProcessor.Flags[utils.MetaRALs]) { @@ -455,9 +475,13 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R agReq.setCGRReply(rply, err) case utils.MetaCDRs: // allow this method case utils.MetaRadauth: - if pass, err := radauthReq(reqProcessor.Flags, req, agReq, rpl); err != nil { + var pass bool + if pass, err = radauthReq(reqProcessor.Flags, req, agReq, rpl); err != nil { + replyState = utils.ErrReplyStateRadauth agReq.CGRReply.Map[utils.Error] = utils.NewLeafNode(err.Error()) } else if !pass { + // Assume failed auth counts as a failed request. + replyState = utils.ErrReplyStateRadauth agReq.CGRReply.Map[utils.Error] = utils.NewLeafNode(utils.RadauthFailed) } } @@ -468,6 +492,11 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R if err = ra.connMgr.Call(ra.ctx, ra.cgrCfg.RadiusAgentCfg().SessionSConns, utils.SessionSv1ProcessCDR, cgrEv, &rplyCDRs); err != nil { agReq.CGRReply.Map[utils.Error] = utils.NewLeafNode(err.Error()) + if replyState == utils.OK { + replyState = utils.ErrReplyStateCDRs + } else { + replyState += ";" + utils.ErrReplyStateCDRs + } } } @@ -501,6 +530,7 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R // asynchronously. ev := cgrEv.Clone() + ev.Event[utils.ReplyState] = replyState ev.Event[utils.StartTime] = startTime ev.Event[utils.EndTime] = endTime ev.Event[utils.ProcessingTime] = endTime.Sub(startTime) diff --git a/agents/sipagent.go b/agents/sipagent.go index 641bd1630..a4c89978b 100644 --- a/agents/sipagent.go +++ b/agents/sipagent.go @@ -379,6 +379,7 @@ func (sa *SIPAgent) handleMessage(sipMessage sipingo.Message, remoteHost string) func (sa *SIPAgent) processRequest(reqProcessor *config.RequestProcessor, agReq *AgentRequest) (processed bool, err error) { startTime := time.Now() + replyState := utils.OK if pass, err := sa.filterS.Pass(agReq.Tenant, reqProcessor.Filters, agReq); err != nil || !pass { return pass, err @@ -440,6 +441,9 @@ func (sa *SIPAgent) processRequest(reqProcessor *config.RequestProcessor, rply := new(sessions.V1AuthorizeReply) err = sa.connMgr.Call(context.TODO(), sa.cfg.SIPAgentCfg().SessionSConns, utils.SessionSv1AuthorizeEvent, authArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateAuthorize + } rply.SetMaxUsageNeeded(authArgs.GetMaxUsage) agReq.setCGRReply(rply, err) case utils.MetaEvent: @@ -452,6 +456,9 @@ func (sa *SIPAgent) processRequest(reqProcessor *config.RequestProcessor, rply := new(sessions.V1ProcessEventReply) err = sa.connMgr.Call(context.TODO(), sa.cfg.SIPAgentCfg().SessionSConns, utils.SessionSv1ProcessEvent, evArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateEvent + } if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) { cgrEv.Event[utils.Usage] = 0 // avoid further debits } else if needsMaxUsage(reqProcessor.Flags[utils.MetaRALs]) { @@ -459,6 +466,7 @@ func (sa *SIPAgent) processRequest(reqProcessor *config.RequestProcessor, } agReq.setCGRReply(rply, err) } + if err := agReq.SetFields(reqProcessor.ReplyFields); err != nil { return false, err } @@ -489,6 +497,7 @@ func (sa *SIPAgent) processRequest(reqProcessor *config.RequestProcessor, // asynchronously. ev := cgrEv.Clone() + ev.Event[utils.ReplyState] = replyState ev.Event[utils.StartTime] = startTime ev.Event[utils.EndTime] = endTime ev.Event[utils.ProcessingTime] = endTime.Sub(startTime) diff --git a/ers/ers.go b/ers/ers.go index 369bbd53c..17fa6ad1f 100644 --- a/ers/ers.go +++ b/ers/ers.go @@ -234,6 +234,54 @@ func (erS *ERService) addReader(rdrID string, cfgIdx int) (err error) { func (erS *ERService) processEvent(cgrEv *utils.CGREvent, rdrCfg *config.EventReaderCfg) (err error) { startTime := time.Now() + replyState := utils.OK + + // Defer stats and thresholds processing to ensure it happens even with early returns. + defer func() { + endTime := time.Now() + if rdrCfg.Flags.Has(utils.MetaDryRun) { + return + } + rawStatIDs := rdrCfg.Flags.ParamValue(utils.MetaERsStats) + rawThIDs := rdrCfg.Flags.ParamValue(utils.MetaERsThresholds) + + // Early return if nothing to process. + if rawStatIDs == "" && rawThIDs == "" { + return + } + + // Clone is needed to prevent data races if requests are sent + // asynchronously. + ev := cgrEv.Clone() + + ev.Event[utils.ReplyState] = replyState + ev.Event[utils.StartTime] = startTime + ev.Event[utils.EndTime] = endTime + ev.Event[utils.ProcessingTime] = endTime.Sub(startTime) + ev.Event[utils.Source] = utils.ERs + ev.APIOpts[utils.MetaEventType] = utils.ProcessTime + + if rawStatIDs != "" { + statIDs := strings.Split(rawStatIDs, utils.ANDSep) + ev.APIOpts[utils.OptsStatsProfileIDs] = statIDs + var reply []string + if err := erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().StatSConns, + utils.StatSv1ProcessEvent, ev, &reply); err != nil { + utils.Logger.Err(fmt.Sprintf("<%s> failed to process event in %s: %v", + utils.ERs, utils.StatS, err)) + } + } + if rawThIDs != "" { + thIDs := strings.Split(rawThIDs, utils.ANDSep) + ev.APIOpts[utils.OptsThresholdsProfileIDs] = thIDs + var reply []string + if err := erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().ThresholdSConns, + utils.ThresholdSv1ProcessEvent, ev, &reply); err != nil { + utils.Logger.Err(fmt.Sprintf("<%s> failed to process event in %s: %v", + utils.ERs, utils.ThresholdS, err)) + } + } + }() // log the event created if requested by flags if rdrCfg.Flags.Has(utils.MetaLog) { utils.Logger.Info( @@ -257,7 +305,8 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, reqType == utils.MetaMessage || reqType == utils.MetaEvent { if cgrArgs, err = utils.GetRoutePaginatorFromOpts(cgrEv.APIOpts); err != nil { - utils.Logger.Warning(fmt.Sprintf("<%s> args extraction for reader <%s> failed because <%s>", + utils.Logger.Warning(fmt.Sprintf( + "<%s> args extraction for reader <%s> failed because <%s>", utils.ERs, rdrCfg.ID, err.Error())) err = nil // reset the error and continue the processing } @@ -291,6 +340,9 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, rply := new(sessions.V1AuthorizeReply) err = erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().SessionSConns, utils.SessionSv1AuthorizeEvent, authArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateAuthorize + } case utils.MetaInitiate: initArgs := sessions.NewV1InitSessionArgs( rdrCfg.Flags.Has(utils.MetaAttributes), @@ -305,6 +357,9 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, rply := new(sessions.V1InitSessionReply) err = erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().SessionSConns, utils.SessionSv1InitiateSession, initArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateInitiate + } case utils.MetaUpdate: updateArgs := sessions.NewV1UpdateSessionArgs( rdrCfg.Flags.Has(utils.MetaAttributes), @@ -318,6 +373,9 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, rply := new(sessions.V1UpdateSessionReply) err = erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().SessionSConns, utils.SessionSv1UpdateSession, updateArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateUpdate + } case utils.MetaTerminate: terminateArgs := sessions.NewV1TerminateSessionArgs( rdrCfg.Flags.Has(utils.MetaAccounts), @@ -330,6 +388,9 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, rply := utils.StringPointer("") err = erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().SessionSConns, utils.SessionSv1TerminateSession, terminateArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateTerminate + } case utils.MetaMessage: evArgs := sessions.NewV1ProcessMessageArgs( rdrCfg.Flags.Has(utils.MetaAttributes), @@ -350,6 +411,9 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, rply := new(sessions.V1ProcessMessageReply) // need it so rpcclient can clone err = erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().SessionSConns, utils.SessionSv1ProcessMessage, evArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateMessage + } if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) { cgrEv.Event[utils.Usage] = 0 // avoid further debits } else if evArgs.Debit { @@ -364,6 +428,9 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, rply := new(sessions.V1ProcessEventReply) err = erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().SessionSConns, utils.SessionSv1ProcessEvent, evArgs, rply) + if err != nil { + replyState = utils.ErrReplyStateEvent + } case utils.MetaCDRs: // allow CDR processing case utils.MetaExport: // allow event exporting } @@ -376,6 +443,7 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, rplyCDRs := utils.StringPointer("") if err := erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().SessionSConns, utils.SessionSv1ProcessCDR, cgrEv, rplyCDRs); err != nil { + replyState = utils.ErrReplyStateCDRs return err } } @@ -387,54 +455,10 @@ func (erS *ERService) processEvent(cgrEv *utils.CGREvent, EeIDs: rdrCfg.EEsIDs, CGREvent: cgrEv, }, &reply); err != nil { + replyState = utils.ErrReplyStateExport return err } } - endTime := time.Now() - if rdrCfg.Flags.Has(utils.MetaDryRun) { - return nil - } - rawStatIDs := rdrCfg.Flags.ParamValue(utils.MetaERsStats) - rawThIDs := rdrCfg.Flags.ParamValue(utils.MetaERsThresholds) - - // Early return if nothing to process. - if rawStatIDs == "" && rawThIDs == "" { - return nil - } - - // Clone is needed to prevent data races if requests are sent - // asynchronously. - ev := cgrEv.Clone() - - ev.Event[utils.StartTime] = startTime - ev.Event[utils.EndTime] = endTime - ev.Event[utils.ProcessingTime] = endTime.Sub(startTime) - ev.Event[utils.Source] = utils.ERs - ev.APIOpts[utils.MetaEventType] = utils.ProcessTime - - if rawStatIDs != "" { - statIDs := strings.Split(rawStatIDs, utils.ANDSep) - ev.APIOpts[utils.OptsStatsProfileIDs] = statIDs - var reply []string - if err := erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().StatSConns, - utils.StatSv1ProcessEvent, ev, &reply); err != nil { - return fmt.Errorf("failed to process event in %s: %v", - utils.StatS, err) - } - // NOTE: ProfileIDs APIOpts key persists for the ThresholdS request, - // although it would be ignored. Might want to delete it. - } - if rawThIDs != "" { - thIDs := strings.Split(rawThIDs, utils.ANDSep) - ev.APIOpts[utils.OptsThresholdsProfileIDs] = thIDs - var reply []string - if err := erS.connMgr.Call(context.TODO(), erS.cfg.ERsCfg().ThresholdSConns, - utils.ThresholdSv1ProcessEvent, ev, &reply); err != nil { - return fmt.Errorf("failed to process event in %s: %v", - utils.ThresholdS, err) - } - } - return } diff --git a/utils/consts.go b/utils/consts.go index abbd58e05..4cb14e62c 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -580,45 +580,58 @@ const ( Initial = "Initial" Action = "Action" - SessionSCosts = "SessionSCosts" - Timing = "Timing" - RQF = "RQF" - Resource = "Resource" - IP = "IP" - User = "User" - Subscribers = "Subscribers" - DerivedChargersV = "DerivedChargers" - Destinations = "Destinations" - ReverseDestinations = "ReverseDestinations" - RatingPlan = "RatingPlan" - RatingProfile = "RatingProfile" - MetaRatingPlans = "*rating_plans" - MetaRatingProfiles = "*rating_profiles" - MetaUsers = "*users" - MetaSubscribers = "*subscribers" - MetaDerivedChargersV = "*derivedchargers" - MetaStorDB = "*stordb" - MetaDataDB = "*datadb" - MetaWeight = "*weight" - MetaLC = "*lc" - MetaHC = "*hc" - MetaQOS = "*qos" - MetaReas = "*reas" - MetaReds = "*reds" - Weight = "Weight" - Limit = "Limit" - UsageTTL = "UsageTTL" - AllocationMessage = "AllocationMessage" - Stored = "Stored" - AddressPool = "AddressPool" - Allocation = "Allocation" - RatingSubject = "RatingSubject" - Categories = "Categories" - Blocker = "Blocker" - RatingPlanID = "RatingPlanID" - StartTime = "StartTime" - EndTime = "EndTime" - ProcessingTime = "ProcessingTime" + SessionSCosts = "SessionSCosts" + Timing = "Timing" + RQF = "RQF" + Resource = "Resource" + IP = "IP" + User = "User" + Subscribers = "Subscribers" + DerivedChargersV = "DerivedChargers" + Destinations = "Destinations" + ReverseDestinations = "ReverseDestinations" + RatingPlan = "RatingPlan" + RatingProfile = "RatingProfile" + MetaRatingPlans = "*rating_plans" + MetaRatingProfiles = "*rating_profiles" + MetaUsers = "*users" + MetaSubscribers = "*subscribers" + MetaDerivedChargersV = "*derivedchargers" + MetaStorDB = "*stordb" + MetaDataDB = "*datadb" + MetaWeight = "*weight" + MetaLC = "*lc" + MetaHC = "*hc" + MetaQOS = "*qos" + MetaReas = "*reas" + MetaReds = "*reds" + Weight = "Weight" + Limit = "Limit" + UsageTTL = "UsageTTL" + AllocationMessage = "AllocationMessage" + Stored = "Stored" + AddressPool = "AddressPool" + Allocation = "Allocation" + RatingSubject = "RatingSubject" + Categories = "Categories" + Blocker = "Blocker" + RatingPlanID = "RatingPlanID" + StartTime = "StartTime" + EndTime = "EndTime" + ProcessingTime = "ProcessingTime" + ReplyState = "ReplyState" + + // ReplyState error constants + ErrReplyStateAuthorize = "ERR_AUTHORIZE" + ErrReplyStateInitiate = "ERR_INITIATE" + ErrReplyStateUpdate = "ERR_UPDATE" + ErrReplyStateTerminate = "ERR_TERMINATE" + ErrReplyStateMessage = "ERR_MESSAGE" + ErrReplyStateEvent = "ERR_EVENT" + ErrReplyStateCDRs = "ERR_CDRS" + ErrReplyStateExport = "ERR_EXPORT" + ErrReplyStateRadauth = "ERR_RADAUTH" + AccountSummary = "AccountSummary" RatingFilters = "RatingFilters" RatingFilter = "RatingFilter"