Files
cgrates/apier/v1/apier.go
2025-02-26 16:19:47 +01:00

2079 lines
69 KiB
Go

/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package v1
import (
"encoding/csv"
"errors"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/ees"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/guardian"
"github.com/cgrates/cgrates/scheduler"
"github.com/cgrates/cgrates/utils"
)
// SchedulerGeter used to avoid ciclic dependency
type SchedulerGeter interface {
GetScheduler() *scheduler.Scheduler
}
type APIerSv1 struct {
StorDb engine.LoadStorage // we should consider keeping only one of StorDB type
CdrDb engine.CdrStorage
DataManager *engine.DataManager
Config *config.CGRConfig
Responder *engine.Responder
SchedulerService SchedulerGeter // Need to have them capitalize so we can export in V2
FilterS *engine.FilterS //Used for CDR Exporter
ConnMgr *engine.ConnManager
StorDBChan chan engine.StorDB
ResponderChan chan *engine.Responder
}
func (apierSv1 *APIerSv1) GetDestination(ctx *context.Context, dstId *string, reply *engine.Destination) error {
if dst, err := apierSv1.DataManager.GetDestination(*dstId, true, true, utils.NonTransactional); err != nil {
return utils.ErrNotFound
} else {
*reply = *dst
}
return nil
}
type AttrRemoveDestination struct {
DestinationIDs []string
Prefixes []string
}
func (apierSv1 *APIerSv1) RemoveDestination(ctx *context.Context, attr *AttrRemoveDestination, reply *string) (err error) {
for _, dstID := range attr.DestinationIDs {
var oldDst *engine.Destination
if oldDst, err = apierSv1.DataManager.GetDestination(dstID, true, true,
utils.NonTransactional); err != nil && err != utils.ErrNotFound {
return
}
if len(attr.Prefixes) != 0 {
newDst := &engine.Destination{
Id: dstID,
Prefixes: make([]string, 0, len(oldDst.Prefixes)),
}
toRemove := utils.NewStringSet(attr.Prefixes)
for _, prfx := range oldDst.Prefixes {
if !toRemove.Has(prfx) {
newDst.Prefixes = append(newDst.Prefixes, prfx)
}
}
if len(newDst.Prefixes) != 0 { // only update the current destination
if err = apierSv1.DataManager.SetDestination(newDst, utils.NonTransactional); err != nil {
return
}
if err = apierSv1.DataManager.UpdateReverseDestination(oldDst, newDst, utils.NonTransactional); err != nil {
return
}
if err = apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
ReverseDestinationIDs: oldDst.Prefixes,
DestinationIDs: []string{dstID},
}, reply); err != nil {
return
}
continue
}
}
if err = apierSv1.DataManager.RemoveDestination(dstID, utils.NonTransactional); err != nil {
return
}
if err = apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
ReverseDestinationIDs: oldDst.Prefixes,
DestinationIDs: []string{dstID},
}, reply); err != nil {
return
}
}
*reply = utils.OK
return
}
// GetReverseDestination retrieves revese destination list for a prefix
func (apierSv1 *APIerSv1) GetReverseDestination(ctx *context.Context, prefix *string, reply *[]string) (err error) {
if *prefix == utils.EmptyString {
return utils.NewErrMandatoryIeMissing("prefix")
}
var revLst []string
if revLst, err = apierSv1.DataManager.GetReverseDestination(*prefix, true, true, utils.NonTransactional); err != nil {
return
}
*reply = revLst
return
}
// ComputeReverseDestinations will rebuild complete reverse destinations data
func (apierSv1 *APIerSv1) ComputeReverseDestinations(ctx *context.Context, ignr *string, reply *string) (err error) {
if err = apierSv1.DataManager.RebuildReverseForPrefix(utils.ReverseDestinationPrefix); err != nil {
return
}
*reply = utils.OK
return
}
// ComputeAccountActionPlans will rebuild complete reverse accountActions data
func (apierSv1 *APIerSv1) ComputeAccountActionPlans(ctx *context.Context, tnt *utils.TenantWithAPIOpts, reply *string) (err error) {
if err = apierSv1.DataManager.RebuildReverseForPrefix(utils.AccountActionPlansPrefix); err != nil {
return
}
return apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1Clear, &utils.AttrCacheIDsWithAPIOpts{
Tenant: tnt.Tenant,
CacheIDs: []string{utils.CacheAccountActionPlans},
APIOpts: tnt.APIOpts,
}, reply)
}
func (apierSv1 *APIerSv1) GetSharedGroup(ctx *context.Context, sgId *string, reply *engine.SharedGroup) error {
if sg, err := apierSv1.DataManager.GetSharedGroup(*sgId, false, utils.NonTransactional); err != nil && err != utils.ErrNotFound { // Not found is not an error here
return err
} else {
if sg != nil {
*reply = *sg
}
}
return nil
}
func (apierSv1 *APIerSv1) SetDestination(ctx *context.Context, attrs *utils.AttrSetDestination, reply *string) (err error) {
if missing := utils.MissingStructFields(attrs, []string{"Id", "Prefixes"}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
dest := &engine.Destination{Id: attrs.Id, Prefixes: attrs.Prefixes}
var oldDest *engine.Destination
if oldDest, err = apierSv1.DataManager.GetDestination(attrs.Id, true, true, utils.NonTransactional); err != nil {
if err != utils.ErrNotFound {
return utils.NewErrServerError(err)
}
} else if !attrs.Overwrite {
return utils.ErrExists
}
if err := apierSv1.DataManager.SetDestination(dest, utils.NonTransactional); err != nil {
return utils.NewErrServerError(err)
}
if err = apierSv1.DataManager.UpdateReverseDestination(oldDest, dest, utils.NonTransactional); err != nil {
return
}
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
ReverseDestinationIDs: dest.Prefixes,
DestinationIDs: []string{attrs.Id},
}, reply); err != nil {
return err
}
*reply = utils.OK
return nil
}
func (apierSv1 *APIerSv1) GetRatingPlan(ctx *context.Context, rplnId *string, reply *engine.RatingPlan) error {
rpln, err := apierSv1.DataManager.GetRatingPlan(*rplnId, false, utils.NonTransactional)
if err != nil {
if err.Error() == utils.ErrNotFound.Error() {
return err
}
return utils.NewErrServerError(err)
}
*reply = *rpln
return nil
}
func (apierSv1 *APIerSv1) RemoveRatingPlan(ctx *context.Context, ID *string, reply *string) error {
if len(*ID) == 0 {
return utils.NewErrMandatoryIeMissing("ID")
}
err := apierSv1.DataManager.RemoveRatingPlan(*ID, utils.NonTransactional)
if err != nil {
return utils.NewErrServerError(err)
}
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
RatingPlanIDs: []string{*ID},
}, reply); err != nil {
return err
}
//generate a loadID for CacheRatingPlans and store it in database
if err := apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheRatingPlans: time.Now().UnixNano()}); err != nil {
return utils.APIErrorHandler(err)
}
*reply = utils.OK
return nil
}
func (apierSv1 *APIerSv1) ExecuteAction(ctx *context.Context, attr *utils.AttrExecuteAction, reply *string) error {
at := &engine.ActionTiming{
ActionsID: attr.ActionsId,
}
tnt := attr.Tenant
if tnt == utils.EmptyString {
tnt = apierSv1.Config.GeneralCfg().DefaultTenant
}
if attr.Account != "" {
at.SetAccountIDs(utils.StringMap{utils.ConcatenatedKey(tnt, attr.Account): true})
}
if err := at.Execute(apierSv1.FilterS, utils.ApierS); err != nil {
*reply = err.Error()
return err
}
*reply = utils.OK
return nil
}
type AttrLoadDestination struct {
TPid string
ID string
}
// Load destinations from storDb into dataDb.
func (apierSv1 *APIerSv1) LoadDestination(ctx *context.Context, attrs *AttrLoadDestination, reply *string) error {
if len(attrs.TPid) == 0 {
return utils.NewErrMandatoryIeMissing("TPid")
}
dbReader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(), apierSv1.StorDb,
attrs.TPid, apierSv1.Config.GeneralCfg().DefaultTimezone, apierSv1.Config.ApierCfg().CachesConns,
apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
if loaded, err := dbReader.LoadDestinationsFiltered(attrs.ID); err != nil {
return utils.NewErrServerError(err)
} else if !loaded {
return utils.ErrNotFound
}
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
DestinationIDs: []string{attrs.ID},
}, reply); err != nil {
return err
}
*reply = utils.OK
return nil
}
type AttrLoadRatingPlan struct {
TPid string
RatingPlanId string
}
// Process dependencies and load a specific rating plan from storDb into dataDb.
func (apierSv1 *APIerSv1) LoadRatingPlan(ctx *context.Context, attrs *AttrLoadRatingPlan, reply *string) error {
if len(attrs.TPid) == 0 {
return utils.NewErrMandatoryIeMissing("TPid")
}
dbReader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(), apierSv1.StorDb,
attrs.TPid, apierSv1.Config.GeneralCfg().DefaultTimezone,
apierSv1.Config.ApierCfg().CachesConns, apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
if loaded, err := dbReader.LoadRatingPlansFiltered(attrs.RatingPlanId); err != nil {
return utils.NewErrServerError(err)
} else if !loaded {
return utils.ErrNotFound
}
*reply = utils.OK
return nil
}
// Process dependencies and load a specific rating profile from storDb into dataDb.
func (apierSv1 *APIerSv1) LoadRatingProfile(ctx *context.Context, attrs *utils.TPRatingProfile, reply *string) error {
if len(attrs.TPid) == 0 {
return utils.NewErrMandatoryIeMissing("TPid")
}
if attrs.Tenant == utils.EmptyString {
attrs.Tenant = apierSv1.Config.GeneralCfg().DefaultTenant
}
dbReader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(), apierSv1.StorDb,
attrs.TPid, apierSv1.Config.GeneralCfg().DefaultTimezone,
apierSv1.Config.ApierCfg().CachesConns, apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
if err := dbReader.LoadRatingProfilesFiltered(attrs); err != nil {
return utils.NewErrServerError(err)
}
if err := apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheRatingProfiles: time.Now().UnixNano()}); err != nil {
return utils.APIErrorHandler(err)
}
// delay if needed before cache reload
if apierSv1.Config.GeneralCfg().CachingDelay != 0 {
utils.Logger.Info(fmt.Sprintf("<LoadRatingProfile> Delaying cache reload for %v", apierSv1.Config.GeneralCfg().CachingDelay))
time.Sleep(apierSv1.Config.GeneralCfg().CachingDelay)
}
if err = dbReader.ReloadCache(config.CgrConfig().GeneralCfg().DefaultCaching, true, make(map[string]any), attrs.Tenant); err != nil {
return utils.NewErrServerError(err)
}
*reply = utils.OK
return nil
}
type AttrLoadSharedGroup struct {
TPid string
SharedGroupId string
}
// Load destinations from storDb into dataDb.
func (apierSv1 *APIerSv1) LoadSharedGroup(ctx *context.Context, attrs *AttrLoadSharedGroup, reply *string) error {
if len(attrs.TPid) == 0 {
return utils.NewErrMandatoryIeMissing("TPid")
}
dbReader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(), apierSv1.StorDb,
attrs.TPid, apierSv1.Config.GeneralCfg().DefaultTimezone,
apierSv1.Config.ApierCfg().CachesConns, apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
if err := dbReader.LoadSharedGroupsFiltered(attrs.SharedGroupId, true); err != nil {
return utils.NewErrServerError(err)
}
*reply = utils.OK
return nil
}
type AttrLoadTpFromStorDb struct {
TPid string
DryRun bool // Only simulate, no write
Validate bool // Run structural checks
APIOpts map[string]any
Caching *string // Caching strategy
}
// Loads complete data in a TP from storDb
func (apierSv1 *APIerSv1) LoadTariffPlanFromStorDb(ctx *context.Context, attrs *AttrLoadTpFromStorDb, reply *string) error {
if len(attrs.TPid) == 0 {
return utils.NewErrMandatoryIeMissing("TPid")
}
dbReader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(), apierSv1.StorDb,
attrs.TPid, apierSv1.Config.GeneralCfg().DefaultTimezone,
apierSv1.Config.ApierCfg().CachesConns, apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
if err := dbReader.LoadAll(); err != nil {
return utils.NewErrServerError(err)
}
if attrs.Validate {
if !dbReader.IsValid() {
*reply = utils.OK
return errors.New("invalid data")
}
}
if attrs.DryRun {
*reply = utils.OK
return nil // Mission complete, no errors
}
if err := dbReader.WriteToDatabase(false, false); err != nil {
return utils.NewErrServerError(err)
}
//verify If Caching is present in arguments
caching := config.CgrConfig().GeneralCfg().DefaultCaching
if attrs.Caching != nil {
caching = *attrs.Caching
}
// delay if needed before cache reload
if apierSv1.Config.GeneralCfg().CachingDelay != 0 {
utils.Logger.Info(fmt.Sprintf("<LoadTariffPlanFromStorDb> Delaying cache reload for %v", apierSv1.Config.GeneralCfg().CachingDelay))
time.Sleep(apierSv1.Config.GeneralCfg().CachingDelay)
}
// reload cache
utils.Logger.Info("APIerSv1.LoadTariffPlanFromStorDb, reloading cache.")
if err := dbReader.ReloadCache(caching, true, attrs.APIOpts, apierSv1.Config.GeneralCfg().DefaultTenant); err != nil {
return utils.NewErrServerError(err)
}
if len(apierSv1.Config.ApierCfg().SchedulerConns) != 0 {
utils.Logger.Info("APIerSv1.LoadTariffPlanFromStorDb, reloading scheduler.")
if err := dbReader.ReloadScheduler(true); err != nil {
return utils.NewErrServerError(err)
}
}
// release the reader with it's structures
dbReader.Init()
*reply = utils.OK
return nil
}
func (apierSv1 *APIerSv1) ImportTariffPlanFromFolder(ctx *context.Context, attrs *utils.AttrImportTPFromFolder, reply *string) error {
if missing := utils.MissingStructFields(attrs, []string{"TPid", "FolderPath"}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
if len(attrs.CsvSeparator) == 0 {
attrs.CsvSeparator = ","
}
if fi, err := os.Stat(attrs.FolderPath); err != nil {
if strings.HasSuffix(err.Error(), "no such file or directory") {
return utils.ErrInvalidPath
}
return utils.NewErrServerError(err)
} else if !fi.IsDir() {
return utils.ErrInvalidPath
}
csvImporter := engine.TPCSVImporter{
TPid: attrs.TPid,
StorDb: apierSv1.StorDb,
DirPath: attrs.FolderPath,
Sep: rune(attrs.CsvSeparator[0]),
Verbose: false,
ImportId: attrs.RunId,
}
if err := csvImporter.Run(); err != nil {
return utils.NewErrServerError(err)
}
*reply = utils.OK
return nil
}
// SetRatingProfile sets a specific rating profile working with data directly in the DataDB without involving storDb
func (apierSv1 *APIerSv1) SetRatingProfile(ctx *context.Context, attrs *utils.AttrSetRatingProfile, reply *string) (err error) {
if missing := utils.MissingStructFields(attrs, []string{"Subject", "RatingPlanActivations"}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
for _, rpa := range attrs.RatingPlanActivations {
if missing := utils.MissingStructFields(rpa, []string{"ActivationTime", "RatingPlanId"}); len(missing) != 0 {
return fmt.Errorf("%s:RatingPlanActivation:%v", utils.ErrMandatoryIeMissing.Error(), missing)
}
}
tnt := attrs.Tenant
if tnt == utils.EmptyString {
tnt = apierSv1.Config.GeneralCfg().DefaultTenant
}
keyID := utils.ConcatenatedKey(utils.MetaOut,
tnt, attrs.Category, attrs.Subject)
var rpfl *engine.RatingProfile
if !attrs.Overwrite {
if rpfl, err = apierSv1.DataManager.GetRatingProfile(keyID, false, utils.NonTransactional); err != nil && err != utils.ErrNotFound {
return utils.NewErrServerError(err)
}
}
if rpfl == nil {
rpfl = &engine.RatingProfile{Id: keyID, RatingPlanActivations: make(engine.RatingPlanActivations, 0)}
}
for _, ra := range attrs.RatingPlanActivations {
at, err := utils.ParseTimeDetectLayout(ra.ActivationTime,
apierSv1.Config.GeneralCfg().DefaultTimezone)
if err != nil {
return fmt.Errorf("%s:Cannot parse activation time from %v", utils.ErrServerError.Error(), ra.ActivationTime)
}
if exists, err := apierSv1.DataManager.HasData(utils.RatingPlanPrefix,
ra.RatingPlanId, ""); err != nil {
return utils.NewErrServerError(err)
} else if !exists {
return fmt.Errorf("%s:RatingPlanId:%s", utils.ErrNotFound.Error(), ra.RatingPlanId)
}
rpfl.RatingPlanActivations = append(rpfl.RatingPlanActivations,
&engine.RatingPlanActivation{
ActivationTime: at,
RatingPlanId: ra.RatingPlanId,
FallbackKeys: utils.FallbackSubjKeys(tnt,
attrs.Category, ra.FallbackSubjects)})
}
if err := apierSv1.DataManager.SetRatingProfile(rpfl); err != nil {
return utils.NewErrServerError(err)
}
//generate a loadID for CacheRatingProfiles and store it in database
if err := apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheRatingProfiles: time.Now().UnixNano()}); err != nil {
return utils.APIErrorHandler(err)
}
// delay if needed before cache call
if apierSv1.Config.GeneralCfg().CachingDelay != 0 {
utils.Logger.Info(fmt.Sprintf("<SetRatingProfile> Delaying cache call for %v", apierSv1.Config.GeneralCfg().CachingDelay))
time.Sleep(apierSv1.Config.GeneralCfg().CachingDelay)
}
if err := apierSv1.CallCache(utils.IfaceAsString(attrs.APIOpts[utils.CacheOpt]), attrs.Tenant, utils.CacheRatingProfiles, keyID, utils.EmptyString, nil, nil, attrs.APIOpts); err != nil {
return utils.APIErrorHandler(err)
}
*reply = utils.OK
return nil
}
// GetRatingProfileIDs returns list of resourceProfile IDs registered for a tenant
func (apierSv1 *APIerSv1) GetRatingProfileIDs(ctx *context.Context, args *utils.PaginatorWithTenant, rsPrfIDs *[]string) error {
tnt := args.Tenant
if tnt == utils.EmptyString {
tnt = apierSv1.Config.GeneralCfg().DefaultTenant
}
prfx := utils.RatingProfilePrefix + "*out:" + tnt + utils.ConcatenatedKeySep
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 {
return utils.ErrNotFound
}
retIDs := make([]string, len(keys))
for i, key := range keys {
retIDs[i] = key[len(prfx):]
}
*rsPrfIDs = args.PaginateStringSlice(retIDs)
return nil
}
func (apierSv1 *APIerSv1) GetRatingProfile(ctx *context.Context, attrs *utils.AttrGetRatingProfile, reply *engine.RatingProfile) (err error) {
if missing := utils.MissingStructFields(attrs, []string{utils.Category, utils.Subject}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
if attrs.Tenant == utils.EmptyString {
attrs.Tenant = apierSv1.Config.GeneralCfg().DefaultTenant
}
if rpPrf, err := apierSv1.DataManager.GetRatingProfile(attrs.GetID(),
false, utils.NonTransactional); err != nil {
return utils.APIErrorHandler(err)
} else {
*reply = *rpPrf
}
return
}
// Deprecated attrs
type V1AttrSetActions struct {
ActionsId string // Actions id
Overwrite bool // If previously defined, will be overwritten
Actions []*V1TPAction // Set of actions this Actions profile will perform
}
type V1TPActions struct {
TPid string // Tariff plan id
ActionsId string // Actions id
Actions []*V1TPAction // Set of actions this Actions profile will perform
}
type V1TPAction struct {
Identifier string // Identifier mapped in the code
BalanceId string // Balance identification string (account scope)
BalanceUuid string // Balance identification string (global scope)
BalanceType string // Type of balance the action will operate on
Units float64 // Number of units to add/deduct
ExpiryTime string // Time when the units will expire
Filters []string // The condition on balances that is checked before the action
TimingTags string // Timing when balance is active
DestinationIds string // Destination profile id
RatingSubject string // Reference a rate subject defined in RatingProfiles
Categories string // category filter for balances
SharedGroups string // Reference to a shared group
BalanceWeight *float64 // Balance weight
ExtraParameters string
BalanceBlocker string
BalanceDisabled string
Weight float64 // Action's weight
}
func (apierSv1 *APIerSv1) SetActions(ctx *context.Context, attrs *V1AttrSetActions, reply *string) (err error) {
if missing := utils.MissingStructFields(attrs, []string{"ActionsId", "Actions"}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
for _, action := range attrs.Actions {
requiredFields := []string{"Identifier", "Weight"}
if action.BalanceType != utils.EmptyString { // Add some inter-dependent parameters - if balanceType then we are not talking about simply calling actions
requiredFields = append(requiredFields, "Units")
}
if missing := utils.MissingStructFields(action, requiredFields); len(missing) != 0 {
return fmt.Errorf("%s:Action:%s:%v", utils.ErrMandatoryIeMissing.Error(), action.Identifier, missing)
}
}
if !attrs.Overwrite {
if exists, err := apierSv1.DataManager.HasData(utils.ActionPrefix, attrs.ActionsId, ""); err != nil {
return utils.NewErrServerError(err)
} else if exists {
return utils.ErrExists
}
}
storeActions := make(engine.Actions, len(attrs.Actions))
for idx, apiAct := range attrs.Actions {
var blocker *bool
if apiAct.BalanceBlocker != utils.EmptyString {
if x, err := strconv.ParseBool(apiAct.BalanceBlocker); err == nil {
blocker = &x
} else {
return err
}
}
var disabled *bool
if apiAct.BalanceDisabled != utils.EmptyString {
if x, err := strconv.ParseBool(apiAct.BalanceDisabled); err == nil {
disabled = &x
} else {
return err
}
}
a := &engine.Action{
Id: attrs.ActionsId,
ActionType: apiAct.Identifier,
Weight: apiAct.Weight,
ExpirationString: apiAct.ExpiryTime,
ExtraParameters: apiAct.ExtraParameters,
Filters: apiAct.Filters,
Balance: &engine.BalanceFilter{ // TODO: update this part
Uuid: utils.StringPointer(apiAct.BalanceUuid),
ID: utils.StringPointer(apiAct.BalanceId),
Type: utils.StringPointer(apiAct.BalanceType),
Value: &utils.ValueFormula{Static: apiAct.Units},
Weight: apiAct.BalanceWeight,
DestinationIDs: utils.StringMapPointer(utils.ParseStringMap(apiAct.DestinationIds)),
RatingSubject: utils.StringPointer(apiAct.RatingSubject),
SharedGroups: utils.StringMapPointer(utils.ParseStringMap(apiAct.SharedGroups)),
Categories: utils.StringMapPointer(utils.ParseStringMap(apiAct.Categories)),
TimingIDs: utils.StringMapPointer(utils.ParseStringMap(apiAct.TimingTags)),
Blocker: blocker,
Disabled: disabled,
},
}
// load action timings from tags
if apiAct.TimingTags != "" {
timingIds := strings.Split(apiAct.TimingTags, utils.InfieldSep)
for _, timingID := range timingIds {
timing, err := apierSv1.DataManager.GetTiming(timingID, false,
utils.NonTransactional)
if err != nil {
return fmt.Errorf("error: %v querying timing with id: %q",
err.Error(), timingID)
}
a.Balance.Timings = append(a.Balance.Timings, &engine.RITiming{
ID: timingID,
Years: timing.Years,
Months: timing.Months,
MonthDays: timing.MonthDays,
WeekDays: timing.WeekDays,
StartTime: timing.StartTime,
EndTime: timing.EndTime,
})
}
}
storeActions[idx] = a
}
if err := apierSv1.DataManager.SetActions(attrs.ActionsId, storeActions); err != nil {
return utils.NewErrServerError(err)
}
//CacheReload
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
ActionIDs: []string{attrs.ActionsId},
}, reply); err != nil {
return err
}
//generate a loadID for CacheActions and store it in database
if err := apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheActions: time.Now().UnixNano()}); err != nil {
return utils.APIErrorHandler(err)
}
*reply = utils.OK
return nil
}
// Retrieves actions attached to specific ActionsId within cache
func (apierSv1 *APIerSv1) GetActions(ctx *context.Context, actsId *string, reply *[]*utils.TPAction) error {
if len(*actsId) == 0 {
return fmt.Errorf("%s ActionsId: %s", utils.ErrMandatoryIeMissing.Error(), *actsId)
}
acts := make([]*utils.TPAction, 0)
engActs, err := apierSv1.DataManager.GetActions(*actsId, false, utils.NonTransactional)
if err != nil {
return utils.NewErrServerError(err)
}
for _, engAct := range engActs {
act := &utils.TPAction{
Identifier: engAct.ActionType,
ExpiryTime: engAct.ExpirationString,
ExtraParameters: engAct.ExtraParameters,
Filters: strings.Join(engAct.Filters, utils.InfieldSep),
Weight: engAct.Weight,
}
bf := engAct.Balance
if bf != nil {
act.BalanceType = bf.GetType()
act.Units = strconv.FormatFloat(bf.GetValue(), 'f', -1, 64)
act.DestinationIds = bf.GetDestinationIDs().String()
act.RatingSubject = bf.GetRatingSubject()
act.SharedGroups = bf.GetSharedGroups().String()
act.BalanceWeight = strconv.FormatFloat(bf.GetWeight(), 'f', -1, 64)
act.TimingTags = bf.GetTimingIDs().String()
act.BalanceId = bf.GetID()
act.Categories = bf.GetCategories().String()
act.BalanceBlocker = strconv.FormatBool(bf.GetBlocker())
act.BalanceDisabled = strconv.FormatBool(bf.GetDisabled())
}
acts = append(acts, act)
}
*reply = acts
return nil
}
type AttrSetActionPlan struct {
Id string // Profile id
ActionPlan []*AttrActionPlan // Set of actions this Actions profile will perform
Overwrite bool // If previously defined, will be overwritten
ReloadScheduler bool // Enables automatic reload of the scheduler (eg: useful when adding a single action timing)
}
type AttrActionPlan struct {
ActionsId string // Actions id
TimingID string // timingID is used to specify the ID of the timing for a corner case ( e.g. *monthly_estimated )
Years string // semicolon separated list of years this timing is valid on, *any or empty supported
Months string // semicolon separated list of months this timing is valid on, *any or empty supported
MonthDays string // semicolon separated list of month's days this timing is valid on, *any or empty supported
WeekDays string // semicolon separated list of week day names this timing is valid on *any or empty supported
Time string // String representing the time this timing starts on, *asap supported
Weight float64 // Binding's weight
}
func (attr *AttrActionPlan) getRITiming(dm *engine.DataManager) (timing *engine.RITiming, err error) {
if dfltTiming, isDefault := checkDefaultTiming(attr.Time); isDefault {
return dfltTiming, nil
}
timing = new(engine.RITiming)
if attr.TimingID != utils.EmptyString &&
!strings.HasPrefix(attr.TimingID, utils.Meta) { // in case of dynamic timing
if dbTiming, err := dm.GetTiming(attr.TimingID, false, utils.NonTransactional); err != nil {
if err != utils.ErrNotFound { // if not found let the user to populate all the timings values
return nil, err
}
} else {
timing.ID = dbTiming.ID
timing.Years = dbTiming.Years
timing.Months = dbTiming.Months
timing.MonthDays = dbTiming.MonthDays
timing.WeekDays = dbTiming.WeekDays
timing.StartTime = dbTiming.StartTime
timing.EndTime = dbTiming.EndTime
}
}
timing.ID = attr.TimingID
timing.Years.Parse(attr.Years, ";")
timing.Months.Parse(attr.Months, ";")
timing.MonthDays.Parse(attr.MonthDays, ";")
timing.WeekDays.Parse(attr.WeekDays, ";")
if !verifyFormat(attr.Time) {
err = fmt.Errorf("%s:%s", utils.ErrUnsupportedFormat.Error(), attr.Time)
return
}
timing.StartTime = attr.Time
return
}
func (apierSv1 *APIerSv1) SetActionPlan(ctx *context.Context, attrs *AttrSetActionPlan, reply *string) (err error) {
if missing := utils.MissingStructFields(attrs, []string{"Id", "ActionPlan"}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
for _, at := range attrs.ActionPlan {
requiredFields := []string{"ActionsId", "Time", "Weight"}
if missing := utils.MissingStructFields(at, requiredFields); len(missing) != 0 {
return fmt.Errorf("%s:Action:%s:%v", utils.ErrMandatoryIeMissing.Error(), at.ActionsId, missing)
}
}
err = guardian.Guardian.Guard(func() error {
var prevAccountIDs utils.StringMap
if prevAP, err := apierSv1.DataManager.GetActionPlan(attrs.Id, true, true, utils.NonTransactional); err != nil && err != utils.ErrNotFound {
return utils.NewErrServerError(err)
} else if err == nil && !attrs.Overwrite {
return utils.ErrExists
} else if prevAP != nil {
prevAccountIDs = prevAP.AccountIDs
}
ap := &engine.ActionPlan{
Id: attrs.Id,
}
for _, apiAtm := range attrs.ActionPlan {
if exists, err := apierSv1.DataManager.HasData(utils.ActionPrefix, apiAtm.ActionsId, ""); err != nil {
return utils.NewErrServerError(err)
} else if !exists {
return fmt.Errorf("%s:%s", utils.ErrBrokenReference.Error(), apiAtm.ActionsId)
}
timing, err := apiAtm.getRITiming(apierSv1.DataManager)
if err != nil {
return err
}
ap.ActionTimings = append(ap.ActionTimings, &engine.ActionTiming{
Uuid: utils.GenUUID(),
Weight: apiAtm.Weight,
Timing: &engine.RateInterval{Timing: timing},
ActionsID: apiAtm.ActionsId,
})
}
if err := apierSv1.DataManager.SetActionPlan(ap.Id, ap, true, utils.NonTransactional); err != nil {
return utils.NewErrServerError(err)
}
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
ActionPlanIDs: []string{ap.Id},
}, reply); err != nil {
return err
}
for acntID := range prevAccountIDs {
if err := apierSv1.DataManager.RemAccountActionPlans(acntID, []string{attrs.Id}); err != nil {
return utils.NewErrServerError(err)
}
}
if len(prevAccountIDs) != 0 {
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
AccountActionPlanIDs: prevAccountIDs.Slice(),
}, reply); err != nil {
return err
}
}
return nil
}, config.CgrConfig().GeneralCfg().LockingTimeout, utils.ActionPlanPrefix)
if err != nil {
return err
}
if attrs.ReloadScheduler {
sched := apierSv1.SchedulerService.GetScheduler()
if sched == nil {
return errors.New(utils.SchedulerNotRunningCaps)
}
sched.Reload()
}
//generate a loadID for CacheActionPlans and store it in database
if err := apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheActionPlans: time.Now().UnixNano()}); err != nil {
return utils.APIErrorHandler(err)
}
*reply = utils.OK
return nil
}
func verifyFormat(tStr string) bool {
if tStr == utils.EmptyString ||
tStr == utils.MetaASAP {
return true
}
if len(tStr) > 8 { // hh:mm:ss
return false
}
if a := strings.Split(tStr, utils.InInFieldSep); len(a) != 3 {
return false
} else {
if _, err := strconv.Atoi(a[0]); err != nil {
return false
} else if _, err := strconv.Atoi(a[1]); err != nil {
return false
} else if _, err := strconv.Atoi(a[2]); err != nil {
return false
}
}
return true
}
// checkDefaultTiming will check the tStr if it's of the the default timings ( the same as in TPReader )
// and will compute it properly
func checkDefaultTiming(tStr string) (rTm *engine.RITiming, isDefault bool) {
currentTime := time.Now()
fmtTime := currentTime.Format("15:04:05")
switch tStr {
case utils.MetaEveryMinute:
return &engine.RITiming{
ID: utils.MetaEveryMinute,
Years: utils.Years{},
Months: utils.Months{},
MonthDays: utils.MonthDays{},
WeekDays: utils.WeekDays{},
StartTime: utils.ConcatenatedKey(utils.Meta, utils.Meta, strconv.Itoa(currentTime.Second())),
EndTime: "",
}, true
case utils.MetaHourly:
return &engine.RITiming{
ID: utils.MetaHourly,
Years: utils.Years{},
Months: utils.Months{},
MonthDays: utils.MonthDays{},
WeekDays: utils.WeekDays{},
StartTime: utils.ConcatenatedKey(utils.Meta, strconv.Itoa(currentTime.Minute()), strconv.Itoa(currentTime.Second())),
EndTime: "",
}, true
case utils.MetaDaily:
return &engine.RITiming{
ID: utils.MetaDaily,
Years: utils.Years{},
Months: utils.Months{},
MonthDays: utils.MonthDays{},
WeekDays: utils.WeekDays{},
StartTime: fmtTime,
EndTime: ""}, true
case utils.MetaWeekly:
return &engine.RITiming{
ID: utils.MetaWeekly,
Years: utils.Years{},
Months: utils.Months{},
MonthDays: utils.MonthDays{},
WeekDays: utils.WeekDays{currentTime.Weekday()},
StartTime: fmtTime,
EndTime: "",
}, true
case utils.MetaMonthly:
return &engine.RITiming{
ID: utils.MetaMonthly,
Years: utils.Years{},
Months: utils.Months{},
MonthDays: utils.MonthDays{currentTime.Day()},
WeekDays: utils.WeekDays{},
StartTime: fmtTime,
EndTime: "",
}, true
case utils.MetaMonthlyEstimated:
return &engine.RITiming{
ID: utils.MetaMonthlyEstimated,
Years: utils.Years{},
Months: utils.Months{},
MonthDays: utils.MonthDays{currentTime.Day()},
WeekDays: utils.WeekDays{},
StartTime: fmtTime,
EndTime: "",
}, true
case utils.MetaMonthEnd:
return &engine.RITiming{
ID: utils.MetaMonthEnd,
Years: utils.Years{},
Months: utils.Months{},
MonthDays: utils.MonthDays{-1},
WeekDays: utils.WeekDays{},
StartTime: fmtTime,
EndTime: "",
}, true
case utils.MetaYearly:
return &engine.RITiming{
ID: utils.MetaYearly,
Years: utils.Years{},
Months: utils.Months{currentTime.Month()},
MonthDays: utils.MonthDays{currentTime.Day()},
WeekDays: utils.WeekDays{},
StartTime: fmtTime,
EndTime: "",
}, true
default:
return nil, false
}
}
type AttrGetActionPlan struct {
ID string
}
func (apierSv1 *APIerSv1) GetActionPlan(ctx *context.Context, attr *AttrGetActionPlan, reply *[]*engine.ActionPlan) error {
var result []*engine.ActionPlan
if attr.ID == "" || attr.ID == "*" {
result = make([]*engine.ActionPlan, 0)
aplsMap, err := apierSv1.DataManager.GetAllActionPlans()
if err != nil {
return err
}
for _, apls := range aplsMap {
result = append(result, apls)
}
} else {
apls, err := apierSv1.DataManager.GetActionPlan(attr.ID, true, true, utils.NonTransactional)
if err != nil {
return err
}
result = append(result, apls)
}
*reply = result
return nil
}
func (apierSv1 *APIerSv1) RemoveActionPlan(ctx *context.Context, attr *AttrGetActionPlan, reply *string) (err error) {
if missing := utils.MissingStructFields(attr, []string{"ID"}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
if err = guardian.Guardian.Guard(func() error {
var prevAccountIDs utils.StringMap
if prevAP, err := apierSv1.DataManager.GetActionPlan(attr.ID, true, true, utils.NonTransactional); err != nil && err != utils.ErrNotFound {
return err
} else if prevAP != nil {
prevAccountIDs = prevAP.AccountIDs
}
if err := apierSv1.DataManager.RemoveActionPlan(attr.ID, utils.NonTransactional); err != nil {
return err
}
for acntID := range prevAccountIDs {
if err := apierSv1.DataManager.RemAccountActionPlans(acntID, []string{attr.ID}); err != nil {
return utils.NewErrServerError(err)
}
}
if len(prevAccountIDs) != 0 {
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
AccountActionPlanIDs: prevAccountIDs.Slice(),
}, reply); err != nil {
return err
}
}
return nil
}, config.CgrConfig().GeneralCfg().LockingTimeout, utils.ActionPlanPrefix); err != nil {
return err
}
*reply = utils.OK
return nil
}
// Process dependencies and load a specific AccountActions profile from storDb into dataDb.
func (apierSv1 *APIerSv1) LoadAccountActions(ctx *context.Context, attrs *utils.TPAccountActions, reply *string) error {
if len(attrs.TPid) == 0 {
return utils.NewErrMandatoryIeMissing("TPid")
}
dbReader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(), apierSv1.StorDb,
attrs.TPid, apierSv1.Config.GeneralCfg().DefaultTimezone,
apierSv1.Config.ApierCfg().CachesConns, apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
if err := guardian.Guardian.Guard(func() error {
return dbReader.LoadAccountActionsFiltered(attrs)
}, config.CgrConfig().GeneralCfg().LockingTimeout, attrs.LoadId); err != nil {
return utils.NewErrServerError(err)
}
// ToDo: Get the action keys loaded by dbReader so we reload only these in cache
// Need to do it before scheduler otherwise actions to run will be unknown
sched := apierSv1.SchedulerService.GetScheduler()
if sched != nil {
sched.Reload()
}
*reply = utils.OK
return nil
}
func (apierSv1 *APIerSv1) LoadTariffPlanFromFolder(ctx *context.Context, attrs *utils.AttrLoadTpFromFolder, reply *string) error {
// verify if FolderPath is present
if len(attrs.FolderPath) == 0 {
return fmt.Errorf("%s:%s", utils.ErrMandatoryIeMissing.Error(), "FolderPath")
}
// check if exists or is valid
if fi, err := os.Stat(attrs.FolderPath); err != nil {
if strings.HasSuffix(err.Error(), "no such file or directory") {
return utils.ErrInvalidPath
}
return utils.NewErrServerError(err)
} else if !fi.IsDir() {
return utils.ErrInvalidPath
}
// initialize CSV storage
csvStorage, err := engine.NewFileCSVStorage(utils.CSVSep, attrs.FolderPath)
if err != nil {
return utils.NewErrServerError(err)
}
// create the TpReader
loader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(),
csvStorage, "", apierSv1.Config.GeneralCfg().DefaultTimezone,
apierSv1.Config.ApierCfg().CachesConns, apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
//Load the data
if err := loader.LoadAll(); err != nil {
return utils.NewErrServerError(err)
}
if attrs.DryRun {
*reply = utils.OK
return nil // Mission complete, no errors
}
if attrs.Validate {
if !loader.IsValid() {
return errors.New("invalid data")
}
}
// write data intro Database
if err := loader.WriteToDatabase(false, false); err != nil {
return utils.NewErrServerError(err)
}
//verify If Caching is present in arguments
caching := config.CgrConfig().GeneralCfg().DefaultCaching
if attrs.Caching != nil {
caching = *attrs.Caching
}
// delay if needed before cache reload
if apierSv1.Config.GeneralCfg().CachingDelay != 0 {
utils.Logger.Info(fmt.Sprintf("<V1LoadTariffPlanFromFolder> Delaying cache reload for %v", apierSv1.Config.GeneralCfg().CachingDelay))
time.Sleep(apierSv1.Config.GeneralCfg().CachingDelay)
}
// reload cache
utils.Logger.Info("APIerSv1.LoadTariffPlanFromFolder, reloading cache.")
if err := loader.ReloadCache(caching, true, attrs.APIOpts, apierSv1.Config.GeneralCfg().DefaultTenant); err != nil {
return utils.NewErrServerError(err)
}
if len(apierSv1.Config.ApierCfg().SchedulerConns) != 0 {
utils.Logger.Info("APIerSv1.LoadTariffPlanFromFolder, reloading scheduler.")
if err := loader.ReloadScheduler(true); err != nil {
return utils.NewErrServerError(err)
}
}
// release the reader with it's structures
loader.Init()
*reply = utils.OK
return nil
}
// RemoveTPFromFolder will load the tarrifplan from folder into TpReader object
// and will delete if from database
func (apierSv1 *APIerSv1) RemoveTPFromFolder(ctx *context.Context, attrs *utils.AttrLoadTpFromFolder, reply *string) error {
// verify if FolderPath is present
if len(attrs.FolderPath) == 0 {
return fmt.Errorf("%s:%s", utils.ErrMandatoryIeMissing.Error(), "FolderPath")
}
// check if exists or is valid
if fi, err := os.Stat(attrs.FolderPath); err != nil {
if strings.HasSuffix(err.Error(), "no such file or directory") {
return utils.ErrInvalidPath
}
return utils.NewErrServerError(err)
} else if !fi.IsDir() {
return utils.ErrInvalidPath
}
// initialize CSV storage
csvStorage, err := engine.NewFileCSVStorage(utils.CSVSep, attrs.FolderPath)
if err != nil {
return utils.NewErrServerError(err)
}
// create the TpReader
loader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(),
csvStorage, "", apierSv1.Config.GeneralCfg().DefaultTimezone,
apierSv1.Config.ApierCfg().CachesConns, apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
//Load the data
if err := loader.LoadAll(); err != nil {
return utils.NewErrServerError(err)
}
if attrs.DryRun {
*reply = utils.OK
return nil // Mission complete, no errors
}
if attrs.Validate {
if !loader.IsValid() {
return errors.New("invalid data")
}
}
// remove data from Database
if err := loader.RemoveFromDatabase(false, false); err != nil {
return utils.NewErrServerError(err)
}
//verify If Caching is present in arguments
caching := config.CgrConfig().GeneralCfg().DefaultCaching
if attrs.Caching != nil {
caching = *attrs.Caching
}
// delay if needed before cache reload
if apierSv1.Config.GeneralCfg().CachingDelay != 0 {
utils.Logger.Info(fmt.Sprintf("<RemoveTPFromFolder> Delaying cache reload for %v", apierSv1.Config.GeneralCfg().CachingDelay))
time.Sleep(apierSv1.Config.GeneralCfg().CachingDelay)
}
// reload cache
utils.Logger.Info("APIerSv1.RemoveTPFromFolder, reloading cache.")
if err := loader.ReloadCache(caching, true, attrs.APIOpts, apierSv1.Config.GeneralCfg().DefaultTenant); err != nil {
return utils.NewErrServerError(err)
}
if len(apierSv1.Config.ApierCfg().SchedulerConns) != 0 {
utils.Logger.Info("APIerSv1.RemoveTPFromFolder, reloading scheduler.")
if err := loader.ReloadScheduler(true); err != nil {
return utils.NewErrServerError(err)
}
}
// release the reader with it's structures
loader.Init()
*reply = utils.OK
return nil
}
// RemoveTPFromStorDB will load the tarrifplan from StorDB into TpReader object
// and will delete if from database
func (apierSv1 *APIerSv1) RemoveTPFromStorDB(ctx *context.Context, attrs *AttrLoadTpFromStorDb, reply *string) error {
if len(attrs.TPid) == 0 {
return utils.NewErrMandatoryIeMissing("TPid")
}
dbReader, err := engine.NewTpReader(apierSv1.DataManager.DataDB(), apierSv1.StorDb,
attrs.TPid, apierSv1.Config.GeneralCfg().DefaultTimezone,
apierSv1.Config.ApierCfg().CachesConns, apierSv1.Config.ApierCfg().SchedulerConns,
apierSv1.Config.DataDbCfg().Type == utils.MetaInternal)
if err != nil {
return utils.NewErrServerError(err)
}
if err := dbReader.LoadAll(); err != nil {
return utils.NewErrServerError(err)
}
if attrs.Validate {
if !dbReader.IsValid() {
*reply = utils.OK
return errors.New("invalid data")
}
}
if attrs.DryRun {
*reply = utils.OK
return nil // Mission complete, no errors
}
// remove data from Database
if err := dbReader.RemoveFromDatabase(false, false); err != nil {
return utils.NewErrServerError(err)
}
//verify If Caching is present in arguments
caching := config.CgrConfig().GeneralCfg().DefaultCaching
if attrs.Caching != nil {
caching = *attrs.Caching
}
// delay if needed before cache reload
if apierSv1.Config.GeneralCfg().CachingDelay != 0 {
utils.Logger.Info(fmt.Sprintf("<RemoveTPFromStorDB> Delaying cache reload for %v", apierSv1.Config.GeneralCfg().CachingDelay))
time.Sleep(apierSv1.Config.GeneralCfg().CachingDelay)
}
// reload cache
utils.Logger.Info("APIerSv1.RemoveTPFromStorDB, reloading cache.")
if err := dbReader.ReloadCache(caching, true, attrs.APIOpts, apierSv1.Config.GeneralCfg().DefaultTenant); err != nil {
return utils.NewErrServerError(err)
}
if len(apierSv1.Config.ApierCfg().SchedulerConns) != 0 {
utils.Logger.Info("APIerSv1.RemoveTPFromStorDB, reloading scheduler.")
if err := dbReader.ReloadScheduler(true); err != nil {
return utils.NewErrServerError(err)
}
}
// release the reader with it's structures
dbReader.Init()
*reply = utils.OK
return nil
}
type AttrRemoveRatingProfile struct {
Tenant string
Category string
Subject string
APIOpts map[string]any
}
func (arrp *AttrRemoveRatingProfile) GetId() (result string) {
result = utils.MetaOut + utils.ConcatenatedKeySep
if arrp.Tenant != utils.EmptyString && arrp.Tenant != utils.MetaAny {
result += arrp.Tenant + utils.ConcatenatedKeySep
} else {
return
}
if arrp.Category != utils.EmptyString && arrp.Category != utils.MetaAny {
result += arrp.Category + utils.ConcatenatedKeySep
} else {
return
}
if arrp.Subject != utils.EmptyString {
result += arrp.Subject
}
return
}
func (apierSv1 *APIerSv1) RemoveRatingProfile(ctx *context.Context, attr *AttrRemoveRatingProfile, reply *string) error {
if attr.Tenant == utils.EmptyString {
attr.Tenant = apierSv1.Config.GeneralCfg().DefaultTenant
}
if (attr.Subject != utils.EmptyString && slices.Contains([]string{attr.Tenant, attr.Category}, utils.EmptyString)) ||
(attr.Category != utils.EmptyString && attr.Tenant == utils.EmptyString) {
return utils.ErrMandatoryIeMissing
}
keyID := attr.GetId()
err := guardian.Guardian.Guard(func() error {
return apierSv1.DataManager.RemoveRatingProfile(keyID)
}, config.CgrConfig().GeneralCfg().LockingTimeout, "RemoveRatingProfile")
if err != nil {
*reply = err.Error()
return utils.NewErrServerError(err)
}
//generate a loadID for CacheActionPlans and store it in database
if err := apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheRatingProfiles: time.Now().UnixNano()}); err != nil {
return utils.APIErrorHandler(err)
}
// delay if needed before cache call
if apierSv1.Config.GeneralCfg().CachingDelay != 0 {
utils.Logger.Info(fmt.Sprintf("<RemoveRatingProfile> Delaying cache call for %v", apierSv1.Config.GeneralCfg().CachingDelay))
time.Sleep(apierSv1.Config.GeneralCfg().CachingDelay)
}
if err := apierSv1.CallCache(utils.IfaceAsString(attr.APIOpts[utils.CacheOpt]), attr.Tenant, utils.CacheRatingProfiles, keyID, utils.EmptyString, nil, nil, attr.APIOpts); err != nil {
return utils.APIErrorHandler(err)
}
*reply = utils.OK
return nil
}
func (apierSv1 *APIerSv1) GetLoadHistory(ctx *context.Context, attrs *utils.Paginator, reply *[]*utils.LoadInstance) error {
nrItems := -1
offset := 0
if attrs.Offset != nil { // For offset we need full data
offset = *attrs.Offset
} else if attrs.Limit != nil {
nrItems = *attrs.Limit
}
loadHist, err := apierSv1.DataManager.DataDB().GetLoadHistory(nrItems, true, utils.NonTransactional)
if err != nil {
return utils.NewErrServerError(err)
}
if attrs.Offset != nil && attrs.Limit != nil { // Limit back to original
nrItems = *attrs.Limit
}
if len(loadHist) == 0 || len(loadHist) <= offset || nrItems == 0 {
return utils.ErrNotFound
}
if offset != 0 {
nrItems = offset + nrItems
}
if nrItems == -1 || nrItems > len(loadHist) { // So we can use it in indexing bellow
nrItems = len(loadHist)
}
*reply = loadHist[offset:nrItems]
return nil
}
type AttrRemoveActions struct {
ActionIDs []string
}
func (apierSv1 *APIerSv1) RemoveActions(ctx *context.Context, attr *AttrRemoveActions, reply *string) error {
if attr.ActionIDs == nil {
err := utils.ErrNotFound
*reply = err.Error()
return err
}
// The check could lead to very long execution time. So we decided to leave it at the user's risck.'
/*
stringMap := utils.NewStringMap(attr.ActionIDs...)
keys, err := apiv1.DataManager.DataDB().GetKeysForPrefix(utils.ActionTriggerPrefix, true)
if err != nil {
*reply = err.Error()
return err
}
for _, key := range keys {
getAttrs, err := apiv1.DataManager.DataDB().GetActionTriggers(key[len(utils.ActionTriggerPrefix):])
if err != nil {
*reply = err.Error()
return err
}
for _, atr := range getAttrs {
if _, found := stringMap[atr.ActionsID]; found {
// found action trigger referencing action; abort
err := fmt.Errorf("action %s refenced by action trigger %s", atr.ActionsID, atr.ID)
*reply = err.Error()
return err
}
}
}
allAplsMap, err := apiv1.DataManager.GetAllActionPlans()
if err != nil && err != utils.ErrNotFound {
*reply = err.Error()
return err
}
for _, apl := range allAplsMap {
for _, atm := range apl.ActionTimings {
if _, found := stringMap[atm.ActionsID]; found {
err := fmt.Errorf("action %s refenced by action plan %s", atm.ActionsID, apl.Id)
*reply = err.Error()
return err
}
}
}
*/
for _, aID := range attr.ActionIDs {
if err := apierSv1.DataManager.RemoveActions(aID); err != nil {
*reply = err.Error()
return err
}
}
//CacheReload
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().CachesConns,
utils.CacheSv1ReloadCache, &utils.AttrReloadCacheWithAPIOpts{
ActionIDs: attr.ActionIDs,
}, reply); err != nil {
return err
}
//generate a loadID for CacheActions and store it in database
if err := apierSv1.DataManager.SetLoadIDs(map[string]int64{utils.CacheActions: time.Now().UnixNano()}); err != nil {
return utils.APIErrorHandler(err)
}
*reply = utils.OK
return nil
}
// ReplayFailedPostsParams contains parameters for replaying failed posts.
type ReplayFailedPostsParams struct {
SourcePath string // path for events to be replayed
FailedPath string // path for events that failed to replay, *none to discard, defaults to SourceDir if empty
Modules []string // list of modules to replay requests for, nil for all
}
// ReplayFailedPosts will repost failed requests found in the FailedRequestsInDir
func (apierSv1 *APIerSv1) ReplayFailedPosts(ctx *context.Context, args ReplayFailedPostsParams, reply *string) error {
// Set default directories if not provided.
if args.SourcePath == "" {
args.SourcePath = apierSv1.Config.GeneralCfg().FailedPostsDir
}
if args.FailedPath == "" {
args.FailedPath = args.SourcePath
}
if err := filepath.WalkDir(args.SourcePath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
utils.Logger.Warning(fmt.Sprintf("<ReplayFailedPosts> failed to access path %s: %v", path, err))
return nil // skip paths that cause an error
}
if d.IsDir() {
return nil // skip directories
}
// Skip files not belonging to the specified modules.
if len(args.Modules) != 0 && !slices.ContainsFunc(args.Modules, func(mod string) bool {
return strings.HasPrefix(d.Name(), mod)
}) {
utils.Logger.Info(fmt.Sprintf("<ReplayFailedPosts> skipping file %s: not found within specified modules", d.Name()))
return nil
}
expEv, err := ees.NewExportEventsFromFile(path)
if err != nil {
return fmt.Errorf("failed to init ExportEvents from %s: %v", path, err)
}
// Determine the failover path.
failoverPath := utils.MetaNone
if args.FailedPath != utils.MetaNone {
failoverPath = filepath.Join(args.FailedPath, d.Name())
}
failedPosts, err := expEv.ReplayFailedPosts(apierSv1.Config.GeneralCfg().PosterAttempts)
if err != nil && failoverPath != utils.MetaNone {
// Write the events that failed to be replayed to the failover directory
if err = failedPosts.WriteToFile(failoverPath); err != nil {
return fmt.Errorf("failed to write the events that failed to be replayed to %s: %v", path, err)
}
}
return nil
}); err != nil {
return utils.NewErrServerError(err)
}
*reply = utils.OK
return nil
}
func (apierSv1 *APIerSv1) GetLoadIDs(ctx *context.Context, args *string, reply *map[string]int64) (err error) {
var loadIDs map[string]int64
if loadIDs, err = apierSv1.DataManager.GetItemLoadIDs(*args, false); err != nil {
return
}
*reply = loadIDs
return
}
type LoadTimeArgs struct {
Timezone string
Item string
}
func (apierSv1 *APIerSv1) GetLoadTimes(ctx *context.Context, args *LoadTimeArgs, reply *map[string]string) (err error) {
if loadIDs, err := apierSv1.DataManager.GetItemLoadIDs(args.Item, false); err != nil {
return err
} else {
provMp := make(map[string]string)
for key, val := range loadIDs {
timeVal, err := utils.ParseTimeDetectLayout(strconv.FormatInt(val, 10), args.Timezone)
if err != nil {
return err
}
provMp[key] = timeVal.String()
}
*reply = provMp
}
return
}
func (apierSv1 *APIerSv1) ComputeActionPlanIndexes(ctx *context.Context, _ string, reply *string) (err error) {
if err = apierSv1.DataManager.RebuildReverseForPrefix(utils.AccountActionPlansPrefix); err != nil {
return err
}
*reply = utils.OK
return nil
}
// GetActionPlanIDs returns list of ActionPlan IDs registered for a tenant
func (apierSv1 *APIerSv1) GetActionPlanIDs(ctx *context.Context, args *utils.PaginatorWithTenant, attrPrfIDs *[]string) error {
prfx := utils.ActionPlanPrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(utils.ActionPlanPrefix)
if err != nil {
return err
}
if len(keys) == 0 {
return utils.ErrNotFound
}
retIDs := make([]string, len(keys))
for i, key := range keys {
retIDs[i] = key[len(prfx):]
}
*attrPrfIDs = args.PaginateStringSlice(retIDs)
return nil
}
// GetRatingPlanIDs returns list of RatingPlan IDs registered for a tenant
func (apierSv1 *APIerSv1) GetRatingPlanIDs(ctx *context.Context, args *utils.PaginatorWithTenant, attrPrfIDs *[]string) error {
prfx := utils.RatingPlanPrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(utils.RatingPlanPrefix)
if err != nil {
return err
}
if len(keys) == 0 {
return utils.ErrNotFound
}
retIDs := make([]string, len(keys))
for i, key := range keys {
retIDs[i] = key[len(prfx):]
}
*attrPrfIDs = args.PaginateStringSlice(retIDs)
return nil
}
// ListenAndServe listen for storbd reload
func (apierSv1 *APIerSv1) ListenAndServe(stopChan chan struct{}) {
utils.Logger.Info(fmt.Sprintf("<%s> starting <%s> subsystem", utils.CoreS, utils.ApierS))
for {
select {
case <-stopChan:
return
case stordb, ok := <-apierSv1.StorDBChan:
if !ok { // the chanel was closed by the shutdown of stordbService
return
}
apierSv1.CdrDb = stordb
apierSv1.StorDb = stordb
case resp := <-apierSv1.ResponderChan:
apierSv1.Responder = resp
}
}
}
// ExportToFolder export specific items (or all items if items is empty) from DataDB back to CSV
func (apierSv1 *APIerSv1) ExportToFolder(ctx *context.Context, arg *utils.ArgExportToFolder, reply *string) error {
// if items is empty we need to export all items
if len(arg.Items) == 0 {
arg.Items = []string{utils.MetaAttributes, utils.MetaChargers, utils.MetaDispatchers,
utils.MetaDispatcherHosts, utils.MetaFilters, utils.MetaResources, utils.MetaStats,
utils.MetaRoutes, utils.MetaThresholds, utils.MetaRankings, utils.MetaTrends}
}
if _, err := os.Stat(arg.Path); os.IsNotExist(err) {
os.Mkdir(arg.Path, os.ModeDir)
}
for _, item := range arg.Items {
switch item {
case utils.MetaAttributes:
prfx := utils.AttributeProfilePrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.AttributesCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.AttributeMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
attPrf, err := apierSv1.DataManager.GetAttributeProfile(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
for _, model := range engine.APItoModelTPAttribute(
engine.AttributeProfileToAPI(attPrf)) {
if record, err := engine.CsvDump(model); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
}
csvWriter.Flush()
case utils.MetaChargers:
prfx := utils.ChargerProfilePrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.ChargersCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.ChargerMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
chrPrf, err := apierSv1.DataManager.GetChargerProfile(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
for _, model := range engine.APItoModelTPCharger(
engine.ChargerProfileToAPI(chrPrf)) {
if record, err := engine.CsvDump(model); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
}
csvWriter.Flush()
case utils.MetaDispatchers:
prfx := utils.DispatcherProfilePrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.DispatcherProfilesCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.DispatcherProfileMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
dpsPrf, err := apierSv1.DataManager.GetDispatcherProfile(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
for _, model := range engine.APItoModelTPDispatcherProfile(
engine.DispatcherProfileToAPI(dpsPrf)) {
if record, err := engine.CsvDump(model); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
}
csvWriter.Flush()
case utils.MetaDispatcherHosts:
prfx := utils.DispatcherHostPrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.DispatcherHostsCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.DispatcherHostMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
dpsPrf, err := apierSv1.DataManager.GetDispatcherHost(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
if record, err := engine.CsvDump(engine.APItoModelTPDispatcherHost(
engine.DispatcherHostToAPI(dpsPrf))); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
csvWriter.Flush()
case utils.MetaFilters:
prfx := utils.FilterPrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.FiltersCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.FilterMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
fltr, err := apierSv1.DataManager.GetFilter(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
for _, model := range engine.APItoModelTPFilter(
engine.FilterToTPFilter(fltr)) {
if record, err := engine.CsvDump(model); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
}
csvWriter.Flush()
case utils.MetaResources:
prfx := utils.ResourceProfilesPrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.ResourcesCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.ResourceMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
resPrf, err := apierSv1.DataManager.GetResourceProfile(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
for _, model := range engine.APItoModelResource(
engine.ResourceProfileToAPI(resPrf)) {
if record, err := engine.CsvDump(model); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
}
csvWriter.Flush()
case utils.MetaStats:
prfx := utils.StatQueueProfilePrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.StatsCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.StatMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
stsPrf, err := apierSv1.DataManager.GetStatQueueProfile(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
for _, model := range engine.APItoModelStats(
engine.StatQueueProfileToAPI(stsPrf)) {
if record, err := engine.CsvDump(model); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
}
csvWriter.Flush()
case utils.MetaRankings:
prfx := utils.RankingsProfilePrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 {
continue
}
f, err := os.Create(path.Join(arg.Path, utils.RankingsCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
if err := csvWriter.Write(engine.RankingsMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
rnkPrf, err := apierSv1.DataManager.GetRankingProfile(tntID[0], tntID[1], true, false, utils.NonTransactional)
if err != nil {
return err
}
mdl := engine.APItoModelTPRanking(engine.RankingProfileToAPI(rnkPrf))
record, err := engine.CsvDump(mdl)
if err != nil {
return err
}
csvWriter.Write(record)
}
csvWriter.Flush()
case utils.MetaTrends:
prfx := utils.TrendsProfilePrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.TrendsCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.TrendsMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
trPrf, err := apierSv1.DataManager.GetTrendProfile(tntID[0], tntID[1], true, false, utils.NonTransactional)
if err != nil {
return err
}
mdl := engine.APItoModelTrends(engine.TrendProfileToAPI(trPrf))
record, err := engine.CsvDump(mdl)
if err != nil {
return err
}
csvWriter.Write(record)
}
csvWriter.Flush()
case utils.MetaRoutes:
prfx := utils.RouteProfilePrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.RoutesCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.RouteMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
spp, err := apierSv1.DataManager.GetRouteProfile(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
for _, model := range engine.APItoModelTPRoutes(
engine.RouteProfileToAPI(spp)) {
if record, err := engine.CsvDump(model); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
}
csvWriter.Flush()
case utils.MetaThresholds:
prfx := utils.ThresholdProfilePrefix
keys, err := apierSv1.DataManager.DataDB().GetKeysForPrefix(prfx)
if err != nil {
return err
}
if len(keys) == 0 { // if we don't find items we skip
continue
}
f, err := os.Create(path.Join(arg.Path, utils.ThresholdsCsv))
if err != nil {
return err
}
defer f.Close()
csvWriter := csv.NewWriter(f)
csvWriter.Comma = utils.CSVSep
//write the header of the file
if err := csvWriter.Write(engine.ThresholdMdls{}.CSVHeader()); err != nil {
return err
}
for _, key := range keys {
tntID := strings.SplitN(key[len(prfx):], utils.InInFieldSep, 2)
thPrf, err := apierSv1.DataManager.GetThresholdProfile(tntID[0], tntID[1],
true, false, utils.NonTransactional)
if err != nil {
return err
}
for _, model := range engine.APItoModelTPThreshold(
engine.ThresholdProfileToAPI(thPrf)) {
if record, err := engine.CsvDump(model); err != nil {
return err
} else if err := csvWriter.Write(record); err != nil {
return err
}
}
}
csvWriter.Flush()
}
}
*reply = utils.OK
return nil
}
func (apierSv1 *APIerSv1) ExportCDRs(ctx *context.Context, args *utils.ArgExportCDRs, reply *map[string]any) (err error) {
if len(apierSv1.Config.ApierCfg().EEsConns) == 0 {
return utils.NewErrNotConnected(utils.EEs)
}
cdrsFltr, err := args.RPCCDRsFilter.AsCDRsFilter(apierSv1.Config.GeneralCfg().DefaultTimezone)
if err != nil {
return utils.NewErrServerError(err)
}
cdrs, _, err := apierSv1.CdrDb.GetCDRs(cdrsFltr, false)
if err != nil {
return err
} else if len(cdrs) == 0 {
return utils.ErrNotFound
}
withErros := false
var rplyCdr map[string]map[string]any
for _, cdr := range cdrs {
argCdr := &engine.CGREventWithEeIDs{
EeIDs: args.ExporterIDs,
CGREvent: cdr.AsCGREvent(),
}
if args.Verbose {
argCdr.CGREvent.APIOpts[utils.OptsEEsVerbose] = struct{}{}
}
if err := apierSv1.ConnMgr.Call(context.TODO(), apierSv1.Config.ApierCfg().EEsConns,
utils.EeSv1ProcessEvent,
argCdr, &rplyCdr); err != nil {
utils.Logger.Warning(fmt.Sprintf("<%s> error: <%s> processing event: <%s> with <%s>",
utils.ApierS, err.Error(), utils.ToJSON(cdr.AsCGREvent()), utils.EEs))
withErros = true
}
}
if withErros {
return utils.ErrPartiallyExecuted
}
// we consider only the last reply because it should have the metrics updated
for exporterID, metrics := range rplyCdr {
(*reply)[exporterID] = metrics
}
return
}
type TimeParams struct {
TimingID string // Holds the TimingID to get from the DataDB
Time string // Time to compare with TimingID
}
// Replies true if the TimingID is active at the specified time, false if not
func (apierSv1 *APIerSv1) TimingIsActiveAt(ctx *context.Context, params TimeParams, reply *bool) (err error) {
timing, err := apierSv1.DataManager.GetTiming(params.TimingID, false, utils.NonTransactional)
if err != nil {
return err
}
if tm, err := utils.ParseTimeDetectLayout(params.Time, apierSv1.Config.GeneralCfg().DefaultTimezone); err != nil {
return err
} else {
*reply = timing.IsActiveAt(tm)
}
return
}