// 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 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)} var dcs utils.DerivedChargers if err := rs.GetDerivedChargers(attrsDC, &dcs); err != nil { rs.getCache().Cache(utils.GET_SESS_RUNS_CACHE_PREFIX+ev.CgrId, &cache2go.CacheItem{ Err: err, }) return err } dcs, _ = dcs.AppendDefaultRun() sesRuns := make([]*SessionRun, 0) for _, dc := range dcs { if !utils.IsSliceMember([]string{utils.META_PREPAID, utils.PREPAID}, ev.GetReqType(dc.ReqTypeField)) { continue // We only consider prepaid sessions } startTime, err := ev.GetAnswerTime(dc.AnswerTimeField, rs.Timezone) if err != nil { rs.getCache().Cache(utils.GET_SESS_RUNS_CACHE_PREFIX+ev.CgrId, &cache2go.CacheItem{ Err: err, }) return errors.New("Error parsing answer event start time") } cd := &CallDescriptor{ Direction: ev.GetDirection(dc.DirectionField), Tenant: ev.GetTenant(dc.TenantField), Category: ev.GetCategory(dc.CategoryField), Subject: ev.GetSubject(dc.SubjectField), Account: ev.GetAccount(dc.AccountField), Destination: ev.GetDestination(dc.DestinationField), TimeStart: startTime} sesRuns = append(sesRuns, &SessionRun{DerivedCharger: dc, CallDescriptor: cd}) } *sRuns = sesRuns rs.getCache().Cache(utils.GET_SESS_RUNS_CACHE_PREFIX+ev.CgrId, &cache2go.CacheItem{ Value: sRuns, }) return nil }
func (sm *FSSessionManager) OnChannelAnswer(ev Event) { if ev.MissingParameter() { sm.DisconnectSession(ev.GetUUID(), MISSING_PARAMETER, "") } if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_setvar %s cgr_reqtype %s\n\n", ev.GetUUID(), ev.GetReqType(""))); err != nil { engine.Logger.Err(fmt.Sprintf("Error on attempting to overwrite cgr_type in chan variables: %v", err)) } 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)} var dcs utils.DerivedChargers if err := sm.rater.GetDerivedChargers(attrsDC, &dcs); err != nil { engine.Logger.Err(fmt.Sprintf("<SessionManager> OnAnswer: could not get derived charging for event %s: %s", ev.GetUUID(), err.Error())) sm.DisconnectSession(ev.GetUUID(), SYSTEM_ERROR, "") // Disconnect the session since we are not able to process sessions return } dcs, _ = dcs.AppendDefaultRun() s := NewSession(ev, sm, dcs) if s != nil { sm.sessions = append(sm.sessions, s) } }
// 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 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), Account: ev.GetAccount(utils.META_DEFAULT), Subject: ev.GetSubject(utils.META_DEFAULT)} var dcs utils.DerivedChargers if err := rs.GetDerivedChargers(attrsDC, &dcs); err != nil { return err } dcs, _ = dcs.AppendDefaultRun() for _, dc := range dcs { if utils.IsSliceMember([]string{utils.META_RATED, utils.RATED}, ev.GetReqType(dc.ReqTypeField)) { // Only consider prepaid and pseudoprepaid for MaxSessionTime continue } runFilters, _ := utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP) matchingAllFilters := true for _, dcRunFilter := range runFilters { if fltrPass, _ := ev.PassesFieldFilter(dcRunFilter); !fltrPass { matchingAllFilters = false break } } if !matchingAllFilters { // Do not process the derived charger further if not all filters were matched continue } startTime, err := ev.GetSetupTime(utils.META_DEFAULT, rs.Timezone) if err != nil { return err } usage, err := ev.GetDuration(utils.META_DEFAULT) if err != nil { return err } if usage == 0 { usage = config.CgrConfig().MaxCallDuration } cd := &CallDescriptor{ Direction: ev.GetDirection(dc.DirectionField), Tenant: ev.GetTenant(dc.TenantField), Category: ev.GetCategory(dc.CategoryField), Subject: ev.GetSubject(dc.SubjectField), Account: ev.GetAccount(dc.AccountField), Destination: ev.GetDestination(dc.DestinationField), TimeStart: startTime, TimeEnd: startTime.Add(usage), } var remainingDuration float64 err = rs.GetMaxSessionTime(cd, &remainingDuration) if err != nil { *reply = 0 return err } if utils.IsSliceMember([]string{utils.META_POSTPAID, utils.POSTPAID}, ev.GetReqType(dc.ReqTypeField)) { // Only consider prepaid and pseudoprepaid for MaxSessionTime continue } // Set maxCallDuration, smallest out of all forked sessions if maxCallDuration == -1.0 { // first time we set it /not initialized yet maxCallDuration = remainingDuration } else if maxCallDuration > remainingDuration { maxCallDuration = remainingDuration } } *reply = maxCallDuration return nil }
// Process Authorize request from OpenSIPS and communicate back maxdur func (osm *OsipsSessionManager) OnAuthorize(osipsDagram *osipsdagram.OsipsEvent) { ev, _ := NewOsipsEvent(osipsDagram) if ev.MissingParameter() { cmdNotify := fmt.Sprintf(":cache_store:\nlocal\n%s/cgr_notify\n%s\n2\n\n", ev.GetUUID(), utils.ERR_MANDATORY_IE_MISSING) if reply, err := osm.miConn.SendCommand([]byte(cmdNotify)); err != nil || !bytes.HasPrefix(reply, []byte("200 OK")) { engine.Logger.Err(fmt.Sprintf("Failed setting cgr_notify variable for accid: %s, err: %v, reply: %s", ev.GetUUID(), err, string(reply))) } return } var maxCallDuration time.Duration // This will be the maximum duration this channel will be allowed to last var durInitialized bool 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)} var dcs utils.DerivedChargers if err := osm.rater.GetDerivedChargers(attrsDC, &dcs); err != nil { engine.Logger.Err(fmt.Sprintf("<SM-OpenSIPS> OnAuthorize: could not get derived charging for event %s: %s", ev.GetUUID(), err.Error())) cmdNotify := fmt.Sprintf(":cache_store:\nlocal\n%s/cgr_notify\n%s\n2\n\n", ev.GetUUID(), utils.ERR_SERVER_ERROR) if reply, err := osm.miConn.SendCommand([]byte(cmdNotify)); err != nil || !bytes.HasPrefix(reply, []byte("200 OK")) { engine.Logger.Err(fmt.Sprintf("Failed setting cgr_notify variable for accid: %s, err: %v, reply: %s", ev.GetUUID(), err, string(reply))) } return } dcs, _ = dcs.AppendDefaultRun() for _, dc := range dcs { runFilters, _ := utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP) matchingAllFilters := true for _, dcRunFilter := range runFilters { if fltrPass, _ := ev.PassesFieldFilter(dcRunFilter); !fltrPass { matchingAllFilters = false break } } if !matchingAllFilters { // Do not process the derived charger further if not all filters were matched continue } startTime, err := ev.GetSetupTime(utils.META_DEFAULT) if err != nil { engine.Logger.Err("Error parsing answer event start time, using time.Now!") startTime = time.Now() } cd := engine.CallDescriptor{ Direction: ev.GetDirection(dc.DirectionField), Tenant: ev.GetTenant(dc.TenantField), Category: ev.GetCategory(dc.CategoryField), Subject: ev.GetSubject(dc.SubjectField), Account: ev.GetAccount(dc.AccountField), Destination: ev.GetDestination(dc.DestinationField), TimeStart: startTime, TimeEnd: startTime.Add(osm.cgrCfg.SMMaxCallDuration), } var remainingDurationFloat float64 err = osm.rater.GetMaxSessionTime(cd, &remainingDurationFloat) if err != nil { engine.Logger.Err(fmt.Sprintf("Could not get max session time for %s: %v", ev.GetUUID(), err)) cmdNotify := fmt.Sprintf(":cache_store:\nlocal\n%s/cgr_notify\n%s\n2\n\n", ev.GetUUID(), utils.ERR_SERVER_ERROR) if reply, err := osm.miConn.SendCommand([]byte(cmdNotify)); err != nil || !bytes.HasPrefix(reply, []byte("200 OK")) { engine.Logger.Err(fmt.Sprintf("Failed setting cgr_notify variable for accid: %s, err: %v, reply: %s", ev.GetUUID(), err, string(reply))) } return } remainingDuration := time.Duration(remainingDurationFloat) // Set maxCallDuration, smallest out of all forked sessions if !durInitialized { // first time we set it /not initialized yet maxCallDuration = remainingDuration durInitialized = true } else if maxCallDuration > remainingDuration { maxCallDuration = remainingDuration } } if maxCallDuration <= osm.cgrCfg.SMMinCallDuration { cmdNotify := fmt.Sprintf(":cache_store:\nlocal\n%s/cgr_notify\n%s\n2\n\n", ev.GetUUID(), OSIPS_INSUFFICIENT_FUNDS) if reply, err := osm.miConn.SendCommand([]byte(cmdNotify)); err != nil || !bytes.HasPrefix(reply, []byte("200 OK")) { engine.Logger.Err(fmt.Sprintf("Failed setting cgr_notify variable for accid: %s, err: %v, reply: %s", ev.GetUUID(), err, string(reply))) } return } cmdMaxDur := fmt.Sprintf(":cache_store:\nlocal\n%s/cgr_maxdur\n%d\n\n", ev.GetUUID(), int(maxCallDuration.Seconds())) if reply, err := osm.miConn.SendCommand([]byte(cmdMaxDur)); err != nil || !bytes.HasPrefix(reply, []byte("200 OK")) { engine.Logger.Err(fmt.Sprintf("Failed setting cgr_maxdur variable for accid: %s, err: %v, reply: %s", ev.GetUUID(), err, string(reply))) } cmdNotify := fmt.Sprintf(":cache_store:\nlocal\n%s/cgr_notify\n%s\n", ev.GetUUID(), OSIPS_AUTH_OK) if reply, err := osm.miConn.SendCommand([]byte(cmdNotify)); err != nil || !bytes.HasPrefix(reply, []byte("200 OK")) { engine.Logger.Err(fmt.Sprintf("Failed setting cgr_notify variable for accid: %s, err: %v, reply: %s", ev.GetUUID(), err, string(reply))) } }
func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { go sm.processCdr(ev.AsStoredCdr()) s := sm.GetSession(ev.GetUUID()) if s == nil { // Not handled by us return } else { sm.RemoveSession(s.uuid) // Unreference it early so we avoid concurrency } defer s.Close(ev) // Stop loop and save the costs deducted so far to database 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)} var dcs utils.DerivedChargers if err := sm.rater.GetDerivedChargers(attrsDC, &dcs); err != nil { engine.Logger.Err(fmt.Sprintf("<SessionManager> OnHangup: could not get derived charging for event %s: %s", ev.GetUUID(), err.Error())) return } dcs, _ = dcs.AppendDefaultRun() for _, dc := range dcs { if ev.GetReqType(dc.ReqTypeField) != utils.PREPAID { continue } sr := s.GetSessionRun(dc.RunId) if sr == nil { continue // Did not save a sessionRun for this dc } if len(sr.callCosts) == 0 { continue // why would we have 0 callcosts } lastCC := sr.callCosts[len(sr.callCosts)-1] lastCC.Timespans.Decompress() // put credit back startTime, err := ev.GetAnswerTime(dc.AnswerTimeField) if err != nil { engine.Logger.Crit("Error parsing prepaid call start time from event") return } duration, err := ev.GetDuration(dc.UsageField) if err != nil { engine.Logger.Crit(fmt.Sprintf("Error parsing call duration from event %s", err.Error())) return } hangupTime := startTime.Add(duration) end := lastCC.Timespans[len(lastCC.Timespans)-1].TimeEnd refundDuration := end.Sub(hangupTime) var refundIncrements engine.Increments for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] tsDuration := ts.GetDuration() if refundDuration <= tsDuration { lastRefundedIncrementIndex := 0 for j := len(ts.Increments) - 1; j >= 0; j-- { increment := ts.Increments[j] if increment.Duration <= refundDuration { refundIncrements = append(refundIncrements, increment) refundDuration -= increment.Duration lastRefundedIncrementIndex = j } } ts.SplitByIncrement(lastRefundedIncrementIndex) break // do not go to other timespans } else { refundIncrements = append(refundIncrements, ts.Increments...) // remove the timespan entirely lastCC.Timespans[i] = nil lastCC.Timespans = lastCC.Timespans[:i] // continue to the next timespan with what is left to refund refundDuration -= tsDuration } } // show only what was actualy refunded (stopped in timespan) // engine.Logger.Info(fmt.Sprintf("Refund duration: %v", initialRefundDuration-refundDuration)) if len(refundIncrements) > 0 { cd := &engine.CallDescriptor{ Direction: lastCC.Direction, Tenant: lastCC.Tenant, Category: lastCC.Category, Subject: lastCC.Subject, Account: lastCC.Account, Destination: lastCC.Destination, Increments: refundIncrements, } var response float64 err := sm.rater.RefundIncrements(*cd, &response) if err != nil { engine.Logger.Err(fmt.Sprintf("Debit cents failed: %v", err)) } } cost := refundIncrements.GetTotalCost() lastCC.Cost -= cost lastCC.Timespans.Compress() } }
func (sm *FSSessionManager) OnChannelPark(ev Event) { var maxCallDuration time.Duration // This will be the maximum duration this channel will be allowed to last var durInitialized bool 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)} var dcs utils.DerivedChargers if err := sm.rater.GetDerivedChargers(attrsDC, &dcs); err != nil { engine.Logger.Err(fmt.Sprintf("<SessionManager> OnPark: could not get derived charging for event %s: %s", ev.GetUUID(), err.Error())) sm.unparkCall(ev.GetUUID(), ev.GetCallDestNr(utils.META_DEFAULT), SYSTEM_ERROR) // We unpark on original destination return } dcs, _ = dcs.AppendDefaultRun() for _, dc := range dcs { runFilters, _ := utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP) matchingAllFilters := true for _, dcRunFilter := range runFilters { if fltrPass, _ := ev.PassesFieldFilter(dcRunFilter); !fltrPass { matchingAllFilters = false break } } if !matchingAllFilters { // Do not process the derived charger further if not all filters were matched continue } startTime, err := ev.GetAnswerTime(PARK_TIME) if err != nil { engine.Logger.Err("Error parsing answer event start time, using time.Now!") startTime = time.Now() } if ev.MissingParameter() { sm.unparkCall(ev.GetUUID(), ev.GetCallDestNr(dc.DestinationField), MISSING_PARAMETER) engine.Logger.Err(fmt.Sprintf("Missing parameter for %s", ev.GetUUID())) return } cd := engine.CallDescriptor{ Direction: ev.GetDirection(dc.DirectionField), Tenant: ev.GetTenant(dc.TenantField), Category: ev.GetCategory(dc.CategoryField), Subject: ev.GetSubject(dc.SubjectField), Account: ev.GetAccount(dc.AccountField), Destination: ev.GetDestination(dc.DestinationField), TimeStart: startTime, TimeEnd: startTime.Add(cfg.SMMaxCallDuration), } var remainingDurationFloat float64 err = sm.rater.GetMaxSessionTime(cd, &remainingDurationFloat) if err != nil { engine.Logger.Err(fmt.Sprintf("Could not get max session time for %s: %v", ev.GetUUID(), err)) sm.unparkCall(ev.GetUUID(), ev.GetCallDestNr(""), SYSTEM_ERROR) // We unpark on original destination return } remainingDuration := time.Duration(remainingDurationFloat) // Set maxCallDuration, smallest out of all forked sessions if !durInitialized { // first time we set it /not initialized yet maxCallDuration = remainingDuration durInitialized = true } else if maxCallDuration > remainingDuration { maxCallDuration = remainingDuration } } if maxCallDuration <= cfg.SMMinCallDuration { //engine.Logger.Info(fmt.Sprintf("Not enough credit for trasferring the call %s for %s.", ev.GetUUID(), cd.GetKey(cd.Subject))) sm.unparkCall(ev.GetUUID(), ev.GetCallDestNr(utils.META_DEFAULT), INSUFFICIENT_FUNDS) return } sm.setMaxCallDuration(ev.GetUUID(), maxCallDuration) sm.unparkCall(ev.GetUUID(), ev.GetCallDestNr(utils.META_DEFAULT), AUTH_OK) }