From 5e4b392bd0ded566137c67e5922a245bc8f2615a Mon Sep 17 00:00:00 2001 From: gezimbll Date: Thu, 14 Dec 2023 06:47:11 -0500 Subject: [PATCH] New admins package --- admins/accounts.go | 163 ++++++++ admins/actions.go | 165 ++++++++ admins/admins.go | 50 +++ admins/attributes.go | 168 ++++++++ admins/cdrs.go | 61 +++ admins/chargers.go | 169 ++++++++ admins/dispatchers.go | 316 +++++++++++++++ admins/filter_indexes.go | 842 +++++++++++++++++++++++++++++++++++++++ admins/filters.go | 210 ++++++++++ admins/libadmin.go | 307 ++++++++++++++ admins/rates.go | 292 ++++++++++++++ admins/resources.go | 168 ++++++++ admins/routes.go | 165 ++++++++ admins/stats.go | 166 ++++++++ admins/thresholds.go | 166 ++++++++ 15 files changed, 3408 insertions(+) create mode 100644 admins/accounts.go create mode 100644 admins/actions.go create mode 100644 admins/admins.go create mode 100644 admins/attributes.go create mode 100644 admins/cdrs.go create mode 100644 admins/chargers.go create mode 100644 admins/dispatchers.go create mode 100644 admins/filter_indexes.go create mode 100644 admins/filters.go create mode 100644 admins/libadmin.go create mode 100644 admins/rates.go create mode 100644 admins/resources.go create mode 100644 admins/routes.go create mode 100644 admins/stats.go create mode 100644 admins/thresholds.go diff --git a/admins/accounts.go b/admins/accounts.go new file mode 100644 index 000000000..1bce51104 --- /dev/null +++ b/admins/accounts.go @@ -0,0 +1,163 @@ +/* +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 +*/ + +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/utils" +) + +// GetAccount returns an Account +func (admS *AdminS) V1GetAccount(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *utils.Account) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + ap, err := admS.dm.GetAccount(ctx, tnt, arg.ID) + if err != nil { + if err.Error() != utils.ErrNotFound.Error() { + err = utils.NewErrServerError(err) + } + return err + } + *reply = *ap + return nil +} + +// GetAccountIDs returns list of account profile IDs registered for a tenant +func (admS *AdminS) V1GetAccountIDs(ctx *context.Context, args *utils.ArgsItemIDs, actPrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.AccountPrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *actPrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetAccounts returns a list of accounts registered for a tenant +func (admS *AdminS) V1GetAccounts(ctx *context.Context, args *utils.ArgsItemIDs, accs *[]*utils.Account) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var accIDs []string + if err = admS.V1GetAccountIDs(ctx, args, &accIDs); err != nil { + return + } + *accs = make([]*utils.Account, 0, len(accIDs)) + for _, accID := range accIDs { + var acc *utils.Account + acc, err = admS.dm.GetAccount(ctx, tnt, accID) + if err != nil { + return utils.APIErrorHandler(err) + } + *accs = append(*accs, acc) + } + return +} + +// GetAccountsCount sets in reply var the total number of AccountIDs registered for a tenant +// returns ErrNotFound in case of 0 AccountIDs +func (admS *AdminS) V1GetAccountsCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.AccountPrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// SetAccount add/update a new Account +func (admS *AdminS) V1SetAccount(ctx *context.Context, args *utils.AccountWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(args.Account, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.SetAccount(ctx, args.Account, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheAccountProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheAccounts: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), args.Tenant, utils.CacheAccounts, + args.TenantID(), utils.EmptyString, &args.FilterIDs, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveAccount remove a specific Account +func (admS *AdminS) V1RemoveAccount(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.RemoveAccount(ctx, tnt, arg.ID, + true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheAccountProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheAccounts: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), tnt, utils.CacheAccounts, + utils.ConcatenatedKey(tnt, arg.ID), utils.EmptyString, nil, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/actions.go b/admins/actions.go new file mode 100644 index 000000000..2202debdb --- /dev/null +++ b/admins/actions.go @@ -0,0 +1,165 @@ +/* +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 +*/ + +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetActionProfile returns an Action Profile +func (admS *AdminS) V1GetActionProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.ActionProfile) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + ap, err := admS.dm.GetActionProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional) + if err != nil { + if err.Error() != utils.ErrNotFound.Error() { + err = utils.NewErrServerError(err) + } + return err + } + *reply = *ap + return nil +} + +// GetActionProfileIDs returns list of action profile IDs registered for a tenant +func (admS *AdminS) V1GetActionProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, actPrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.ActionProfilePrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *actPrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetActionProfiles returns a list of action profiles registered for a tenant +func (admS *AdminS) V1GetActionProfiles(ctx *context.Context, args *utils.ArgsItemIDs, actPrfs *[]*engine.ActionProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var actPrfIDs []string + if err = admS.V1GetActionProfileIDs(ctx, args, &actPrfIDs); err != nil { + return + } + *actPrfs = make([]*engine.ActionProfile, 0, len(actPrfIDs)) + for _, actPrfID := range actPrfIDs { + var ap *engine.ActionProfile + ap, err = admS.dm.GetActionProfile(ctx, tnt, actPrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *actPrfs = append(*actPrfs, ap) + } + return +} + +// GetActionProfilesCount sets in reply var the total number of ActionProfileIDs registered for a tenant +// returns ErrNotFound in case of 0 ActionProfileIDs +func (admS *AdminS) V1GetActionProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.ActionProfilePrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// SetActionProfile add/update a new Action Profile +func (admS *AdminS) V1SetActionProfile(ctx *context.Context, ap *engine.ActionProfileWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(ap.ActionProfile, []string{utils.ID, utils.Actions}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if ap.Tenant == utils.EmptyString { + ap.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + + if err := admS.dm.SetActionProfile(ctx, ap.ActionProfile, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheActionProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheActionProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(ap.APIOpts[utils.MetaCache]), ap.Tenant, utils.CacheActionProfiles, + ap.TenantID(), utils.EmptyString, &ap.FilterIDs, ap.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveActionProfile remove a specific Action Profile +func (admS *AdminS) V1RemoveActionProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.RemoveActionProfile(ctx, tnt, arg.ID, + true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheActionProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheActionProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), tnt, utils.CacheActionProfiles, + utils.ConcatenatedKey(tnt, arg.ID), utils.EmptyString, nil, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/admins.go b/admins/admins.go new file mode 100644 index 000000000..1fe1020c4 --- /dev/null +++ b/admins/admins.go @@ -0,0 +1,50 @@ +/* +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 +*/ + +package admins + +import ( + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" +) + +func NewAdminS(cfg *config.CGRConfig, dm *engine.DataManager, connMgr *engine.ConnManager, fltrS *engine.FilterS, + storDBChan chan engine.StorDB) *AdminS { + storDB := <-storDBChan + return &AdminS{ + cfg: cfg, + dm: dm, + storDB: storDB, + connMgr: connMgr, + fltrS: fltrS, + + // TODO: Might be a good idea to pass the storDB channel to AdminSv1 + // to be able to close the service the moment storDB is down (inside + // a ListenAndServe goroutine maybe) + + // storDBChan: storDBChan, + } +} + +type AdminS struct { + cfg *config.CGRConfig + dm *engine.DataManager + storDB engine.StorDB + connMgr *engine.ConnManager + fltrS *engine.FilterS +} diff --git a/admins/attributes.go b/admins/attributes.go new file mode 100644 index 000000000..464850b1b --- /dev/null +++ b/admins/attributes.go @@ -0,0 +1,168 @@ +/* +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 +*/ + +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetAttributeProfile returns an Attribute Profile based on the tenant and ID received +func (admS *AdminS) V1GetAttributeProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.APIAttributeProfile) (err error) { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + if attrPrf, err := admS.dm.GetAttributeProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional); err != nil { + return utils.APIErrorHandler(err) + } else { + attr := engine.NewAPIAttributeProfile(attrPrf) + *reply = *attr + } + return nil +} + +// GetAttributeProfileIDs returns list of attributeProfile IDs registered for a tenant +func (admS *AdminS) V1GetAttributeProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, attrPrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.AttributeProfilePrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *attrPrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetAttributeProfiles returns a list of attribute profiles registered for a tenant +func (admS *AdminS) V1GetAttributeProfiles(ctx *context.Context, args *utils.ArgsItemIDs, attrPrfs *[]*engine.APIAttributeProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var attrPrfIDs []string + if err = admS.V1GetAttributeProfileIDs(ctx, args, &attrPrfIDs); err != nil { + return + } + *attrPrfs = make([]*engine.APIAttributeProfile, 0, len(attrPrfIDs)) + for _, attrPrfID := range attrPrfIDs { + var ap *engine.AttributeProfile + ap, err = admS.dm.GetAttributeProfile(ctx, tnt, attrPrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + attr := engine.NewAPIAttributeProfile(ap) + *attrPrfs = append(*attrPrfs, attr) + } + return +} + +// GetAttributeProfilesCount returns the total number of AttributeProfileIDs registered for a tenant +// returns ErrNotFound in case of 0 AttributeProfileIDs +func (admS *AdminS) V1GetAttributeProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.AttributeProfilePrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// SetAttributeProfile add/update a new Attribute Profile +func (admS *AdminS) V1SetAttributeProfile(ctx *context.Context, arg *engine.APIAttributeProfileWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg.APIAttributeProfile, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if arg.Tenant == utils.EmptyString { + arg.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + alsPrf, err := arg.APIAttributeProfile.AsAttributeProfile() + if err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.dm.SetAttributeProfile(ctx, alsPrf, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheAttributeProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, + map[string]int64{utils.CacheAttributeProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), alsPrf.Tenant, utils.CacheAttributeProfiles, + alsPrf.TenantID(), utils.EmptyString, &alsPrf.FilterIDs, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveAttributeProfile remove a specific Attribute Profile based on tenant an ID +func (admS *AdminS) V1RemoveAttributeProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.RemoveAttributeProfile(ctx, tnt, arg.ID, + true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheAttributeProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheAttributeProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), tnt, utils.CacheAttributeProfiles, + utils.ConcatenatedKey(tnt, arg.ID), utils.EmptyString, nil, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/cdrs.go b/admins/cdrs.go new file mode 100644 index 000000000..8568702d2 --- /dev/null +++ b/admins/cdrs.go @@ -0,0 +1,61 @@ +/* +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 +*/ + +package admins + +import ( + "fmt" + + "github.com/cgrates/birpc/context" + + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetCDRs retrieves a list of CDRs matching the specified filters. +func (admS AdminS) V1GetCDRs(ctx *context.Context, args *utils.CDRFilters, reply *[]*utils.CDR) error { + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + fltrs, err := engine.GetFilters(ctx, args.FilterIDs, args.Tenant, admS.dm) + if err != nil { + return fmt.Errorf("preparing filters failed: %w", err) + } + cdrs, err := admS.storDB.GetCDRs(ctx, fltrs, args.APIOpts) + if err != nil { + return fmt.Errorf("retrieving CDRs failed: %w", err) + } + *reply = cdrs + return nil +} + +// RemoveCDRs removes CDRs matching the specified filters. +func (admS AdminS) V1RemoveCDRs(ctx *context.Context, args *utils.CDRFilters, reply *string) (err error) { + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + fltrs, err := engine.GetFilters(ctx, args.FilterIDs, args.Tenant, admS.dm) + if err != nil { + return fmt.Errorf("preparing filters failed: %w", err) + } + if err := admS.storDB.RemoveCDRs(ctx, fltrs); err != nil { + return fmt.Errorf("removing CDRs failed: %w", err) + } + *reply = utils.OK + return +} diff --git a/admins/chargers.go b/admins/chargers.go new file mode 100644 index 000000000..d0d21b2f4 --- /dev/null +++ b/admins/chargers.go @@ -0,0 +1,169 @@ +/* +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 +*/ + +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetChargerProfile returns a Charger Profile +func (adms *AdminS) V1GetChargerProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.ChargerProfile) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if cpp, err := adms.dm.GetChargerProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional); err != nil { + return utils.APIErrorHandler(err) + } else { + *reply = *cpp + } + return nil +} + +// GetChargerProfileIDs returns list of chargerProfile IDs registered for a tenant +func (adms *AdminS) V1GetChargerProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, chPrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.ChargerProfilePrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = adms.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *chPrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetChargerProfiles returns a list of charger profiles registered for a tenant +func (admS *AdminS) V1GetChargerProfiles(ctx *context.Context, args *utils.ArgsItemIDs, chrgPrfs *[]*engine.ChargerProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var chrgPrfIDs []string + if err = admS.V1GetChargerProfileIDs(ctx, args, &chrgPrfIDs); err != nil { + return + } + *chrgPrfs = make([]*engine.ChargerProfile, 0, len(chrgPrfIDs)) + for _, chrgPrfID := range chrgPrfIDs { + var chgrPrf *engine.ChargerProfile + chgrPrf, err = admS.dm.GetChargerProfile(ctx, tnt, chrgPrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *chrgPrfs = append(*chrgPrfs, chgrPrf) + } + return +} + +// GetChargerProfilesCount returns the total number of ChargerProfiles registered for a tenant +// returns ErrNotFound in case of 0 ChargerProfiles +func (admS *AdminS) V1GetChargerProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.ChargerProfilePrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +type ChargerWithAPIOpts struct { + *engine.ChargerProfile + APIOpts map[string]any +} + +// SetChargerProfile add/update a new Charger Profile +func (adms *AdminS) V1SetChargerProfile(ctx *context.Context, arg *ChargerWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg.ChargerProfile, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if arg.Tenant == utils.EmptyString { + arg.Tenant = adms.cfg.GeneralCfg().DefaultTenant + } + if err := adms.dm.SetChargerProfile(ctx, arg.ChargerProfile, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheChargerProfiles and store it in database + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheChargerProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for ChargerProfile + if err := adms.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), arg.Tenant, utils.CacheChargerProfiles, + arg.TenantID(), utils.EmptyString, &arg.FilterIDs, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveChargerProfile remove a specific Charger Profile +func (adms *AdminS) V1RemoveChargerProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if err := adms.dm.RemoveChargerProfile(ctx, tnt, + arg.ID, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheChargerProfiles and store it in database + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheChargerProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for ChargerProfile + if err := adms.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), tnt, utils.CacheChargerProfiles, + utils.ConcatenatedKey(tnt, arg.ID), utils.EmptyString, nil, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/dispatchers.go b/admins/dispatchers.go new file mode 100644 index 000000000..d7cd500b1 --- /dev/null +++ b/admins/dispatchers.go @@ -0,0 +1,316 @@ +/* +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 +*/ + +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetDispatcherProfile returns a Dispatcher Profile +func (admS *AdminS) V1GetDispatcherProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.DispatcherProfile) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + dpp, err := admS.dm.GetDispatcherProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *reply = *dpp + return nil +} + +// GetDispatcherProfileIDs returns list of dispatcherProfile IDs registered for a tenant +func (admS *AdminS) V1GetDispatcherProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, dPrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.DispatcherProfilePrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *dPrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetDispatcherProfiles returns a list of dispatcher profiles registered for a tenant +func (admS *AdminS) V1GetDispatcherProfiles(ctx *context.Context, args *utils.ArgsItemIDs, dspPrfs *[]*engine.DispatcherProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var dspPrfIDs []string + if err = admS.V1GetDispatcherProfileIDs(ctx, args, &dspPrfIDs); err != nil { + return + } + *dspPrfs = make([]*engine.DispatcherProfile, 0, len(dspPrfIDs)) + for _, dspPrfID := range dspPrfIDs { + var dspPrf *engine.DispatcherProfile + dspPrf, err = admS.dm.GetDispatcherProfile(ctx, tnt, dspPrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *dspPrfs = append(*dspPrfs, dspPrf) + } + return +} + +// GetDispatcherProfilesCount returns the total number of DispatcherProfiles registered for a tenant +// returns ErrNotFound in case of 0 DispatcherProfiles +func (admS *AdminS) V1GetDispatcherProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.DispatcherProfilePrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +type DispatcherWithAPIOpts struct { + *engine.DispatcherProfile + APIOpts map[string]any +} + +// SetDispatcherProfile add/update a new Dispatcher Profile +func (admS *AdminS) V1SetDispatcherProfile(ctx *context.Context, args *DispatcherWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(args.DispatcherProfile, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.SetDispatcherProfile(ctx, args.DispatcherProfile, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheDispatcherProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheDispatcherProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for DispatcherProfile + if err := admS.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), args.Tenant, utils.CacheDispatcherProfiles, + args.TenantID(), utils.EmptyString, &args.FilterIDs, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for DispatchersInstance + cacheAct := utils.MetaRemove + if err := admS.CallCache(ctx, utils.FirstNonEmpty(utils.IfaceAsString(args.APIOpts[utils.MetaCache]), cacheAct), + args.Tenant, utils.CacheDispatchers, args.TenantID(), utils.EmptyString, &args.FilterIDs, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for DispatcherRoutes + if err := admS.CallCache(ctx, utils.FirstNonEmpty(utils.IfaceAsString(args.APIOpts[utils.MetaCache]), cacheAct), + args.Tenant, utils.CacheDispatcherRoutes, args.TenantID(), + utils.ConcatenatedKey(utils.CacheDispatcherProfiles, args.Tenant, args.ID), + &args.FilterIDs, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveDispatcherProfile remove a specific Dispatcher Profile +func (admS *AdminS) V1RemoveDispatcherProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.RemoveDispatcherProfile(ctx, tnt, + arg.ID, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheDispatcherProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheDispatcherProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for DispatcherProfile + if err := admS.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), tnt, utils.CacheDispatcherProfiles, + utils.ConcatenatedKey(tnt, arg.ID), utils.EmptyString, nil, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// GetDispatcherHost returns a Dispatcher Host +func (admS *AdminS) V1GetDispatcherHost(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.DispatcherHost) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + dpp, err := admS.dm.GetDispatcherHost(ctx, tnt, arg.ID, true, false, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *reply = *dpp + return nil +} + +// GetDispatcherHostIDs returns list of dispatcherHost IDs registered for a tenant +func (admS *AdminS) V1GetDispatcherHostIDs(ctx *context.Context, args *utils.ArgsItemIDs, dspHostIDs *[]string) (err error) { + tenant := args.Tenant + if tenant == utils.EmptyString { + tenant = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.DispatcherHostPrefix + tenant + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *dspHostIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetDispatcherHosts returns a list of dispatcher hosts registered for a tenant +func (admS *AdminS) V1GetDispatcherHosts(ctx *context.Context, args *utils.ArgsItemIDs, dspHosts *[]*engine.DispatcherHost) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var dspHostIDs []string + if err = admS.V1GetDispatcherHostIDs(ctx, args, &dspHostIDs); err != nil { + return + } + *dspHosts = make([]*engine.DispatcherHost, 0, len(dspHostIDs)) + for _, dspHostID := range dspHostIDs { + var dspHost *engine.DispatcherHost + dspHost, err = admS.dm.GetDispatcherHost(ctx, tnt, dspHostID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *dspHosts = append(*dspHosts, dspHost) + } + return +} + +// GetDispatcherHostsCount returns the total number of DispatcherHosts registered for a tenant +// returns ErrNotFound in case of 0 DispatcherHosts +func (admS *AdminS) V1GetDispatcherHostsCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.DispatcherHostPrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// SetDispatcherHost add/update a new Dispatcher Host +func (admS *AdminS) V1SetDispatcherHost(ctx *context.Context, args *engine.DispatcherHostWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(args.DispatcherHost, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.SetDispatcherHost(ctx, args.DispatcherHost); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheDispatcherHosts and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheDispatcherHosts: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for DispatcherProfile + if err := admS.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), args.Tenant, utils.CacheDispatcherHosts, + args.TenantID(), utils.EmptyString, nil, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveDispatcherHost remove a specific Dispatcher Host +func (admS *AdminS) V1RemoveDispatcherHost(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.RemoveDispatcherHost(ctx, tnt, arg.ID); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheDispatcherHosts and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheDispatcherHosts: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for DispatcherProfile + if err := admS.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), tnt, utils.CacheDispatcherHosts, + utils.ConcatenatedKey(tnt, arg.ID), utils.EmptyString, nil, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/filter_indexes.go b/admins/filter_indexes.go new file mode 100644 index 000000000..c70cd0bf8 --- /dev/null +++ b/admins/filter_indexes.go @@ -0,0 +1,842 @@ +/* +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 +*/ + +package admins + +import ( + "slices" + "strings" + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/ltcache" +) + +type AttrGetFilterIndexes struct { + Tenant string + Context string + ItemType string + FilterType string + FilterField string + FilterValue string + APIOpts map[string]any +} + +type AttrRemFilterIndexes struct { + Tenant string + Context string + ItemType string + APIOpts map[string]any +} + +func (adms *AdminS) V1RemoveFilterIndexes(ctx *context.Context, arg *AttrRemFilterIndexes, reply *string) (err error) { + if missing := utils.MissingStructFields(arg, []string{"ItemType"}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + tntCtx := tnt + switch arg.ItemType { + case utils.MetaThresholds: + arg.ItemType = utils.CacheThresholdFilterIndexes + case utils.MetaRoutes: + arg.ItemType = utils.CacheRouteFilterIndexes + case utils.MetaStats: + arg.ItemType = utils.CacheStatFilterIndexes + case utils.MetaResources: + arg.ItemType = utils.CacheResourceFilterIndexes + case utils.MetaChargers: + arg.ItemType = utils.CacheChargerFilterIndexes + case utils.MetaAccounts: + arg.ItemType = utils.CacheAccountsFilterIndexes + case utils.MetaActions: + arg.ItemType = utils.CacheActionProfilesFilterIndexes + case utils.MetaRateProfiles: + arg.ItemType = utils.CacheRateProfilesFilterIndexes + case utils.MetaRateProfileRates: + if missing := utils.MissingStructFields(arg, []string{"Context"}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + arg.ItemType = utils.CacheRateFilterIndexes + tntCtx = utils.ConcatenatedKey(tnt, arg.Context) + case utils.MetaDispatchers: + arg.ItemType = utils.CacheDispatcherFilterIndexes + case utils.MetaAttributes: + arg.ItemType = utils.CacheAttributeFilterIndexes + } + if err = adms.dm.RemoveIndexes(ctx, arg.ItemType, tntCtx, utils.EmptyString); err != nil { + return + } + //generate a loadID for CacheFilterIndexes and store it in database + if err := adms.dm.SetLoadIDs(ctx, + map[string]int64{arg.ItemType: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := adms.callCacheForRemoveIndexes(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), arg.Tenant, + arg.ItemType, []string{utils.MetaAny}, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return +} + +func (adms *AdminS) V1GetFilterIndexes(ctx *context.Context, arg *AttrGetFilterIndexes, reply *[]string) (err error) { + var indexes map[string]utils.StringSet + var indexedSlice []string + indexesFilter := make(map[string]utils.StringSet) + if missing := utils.MissingStructFields(arg, []string{"ItemType"}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + tntCtx := tnt + switch arg.ItemType { + case utils.MetaThresholds: + arg.ItemType = utils.CacheThresholdFilterIndexes + case utils.MetaRoutes: + arg.ItemType = utils.CacheRouteFilterIndexes + case utils.MetaStats: + arg.ItemType = utils.CacheStatFilterIndexes + case utils.MetaResources: + arg.ItemType = utils.CacheResourceFilterIndexes + case utils.MetaChargers: + arg.ItemType = utils.CacheChargerFilterIndexes + case utils.MetaAccounts: + arg.ItemType = utils.CacheAccountsFilterIndexes + case utils.MetaActions: + arg.ItemType = utils.CacheActionProfilesFilterIndexes + case utils.MetaRateProfiles: + arg.ItemType = utils.CacheRateProfilesFilterIndexes + case utils.MetaRateProfileRates: + if missing := utils.MissingStructFields(arg, []string{"Context"}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + arg.ItemType = utils.CacheRateFilterIndexes + tntCtx = utils.ConcatenatedKey(tnt, arg.Context) + case utils.MetaDispatchers: + arg.ItemType = utils.CacheDispatcherFilterIndexes + case utils.MetaAttributes: + arg.ItemType = utils.CacheAttributeFilterIndexes + } + if indexes, err = adms.dm.GetIndexes(ctx, + arg.ItemType, tntCtx, utils.EmptyString, utils.EmptyString, true, true); err != nil { + return + } + if arg.FilterType != utils.EmptyString { + for val, strmap := range indexes { + if strings.HasPrefix(val, arg.FilterType) { + indexesFilter[val] = strmap + for _, value := range strmap.AsSlice() { + indexedSlice = append(indexedSlice, utils.ConcatenatedKey(val, value)) + } + } + } + if len(indexedSlice) == 0 { + return utils.ErrNotFound + } + } + if arg.FilterField != utils.EmptyString { + if len(indexedSlice) == 0 { + indexesFilter = make(map[string]utils.StringSet) + for val, strmap := range indexes { + if strings.Contains(val, arg.FilterField) { + indexesFilter[val] = strmap + for _, value := range strmap.AsSlice() { + indexedSlice = append(indexedSlice, utils.ConcatenatedKey(val, value)) + } + } + } + if len(indexedSlice) == 0 { + return utils.ErrNotFound + } + } else { + var cloneIndexSlice []string + for val, strmap := range indexesFilter { + if strings.Contains(val, arg.FilterField) { + for _, value := range strmap.AsSlice() { + cloneIndexSlice = append(cloneIndexSlice, utils.ConcatenatedKey(val, value)) + } + } + } + if len(cloneIndexSlice) == 0 { + return utils.ErrNotFound + } + indexedSlice = cloneIndexSlice + } + } + if arg.FilterValue != utils.EmptyString { + if len(indexedSlice) == 0 { + for val, strmap := range indexes { + if strings.Contains(val, arg.FilterValue) { + for _, value := range strmap.AsSlice() { + indexedSlice = append(indexedSlice, utils.ConcatenatedKey(val, value)) + } + } + } + if len(indexedSlice) == 0 { + return utils.ErrNotFound + } + } else { + var cloneIndexSlice []string + for val, strmap := range indexesFilter { + if strings.Contains(val, arg.FilterValue) { + for _, value := range strmap.AsSlice() { + cloneIndexSlice = append(cloneIndexSlice, utils.ConcatenatedKey(val, value)) + } + } + } + if len(cloneIndexSlice) == 0 { + return utils.ErrNotFound + } + indexedSlice = cloneIndexSlice + } + } + if len(indexedSlice) == 0 { + for val, strmap := range indexes { + for _, value := range strmap.AsSlice() { + indexedSlice = append(indexedSlice, utils.ConcatenatedKey(val, value)) + } + } + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(arg.APIOpts); err != nil { + return + } + *reply, err = utils.Paginate(indexedSlice, limit, offset, maxItems) + return +} + +// ComputeFilterIndexes selects which index filters to recompute +func (adms *AdminS) V1ComputeFilterIndexes(ctx *context.Context, args *utils.ArgsComputeFilterIndexes, reply *string) (err error) { + transactionID := utils.GenUUID() + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + cacheIDs := make(map[string][]string) + + var indexes utils.StringSet + //ThresholdProfile Indexes + if args.ThresholdS { + cacheIDs[utils.CacheThresholdFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheThresholdFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + th, e := adms.dm.GetThresholdProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(th.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + args.ThresholdS = indexes.Size() != 0 + } + //StatQueueProfile Indexes + if args.StatS { + cacheIDs[utils.CacheStatFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheStatFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + sq, e := adms.dm.GetStatQueueProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(sq.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + args.StatS = indexes.Size() != 0 + } + //ResourceProfile Indexes + if args.ResourceS { + cacheIDs[utils.CacheResourceFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheResourceFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + rp, e := adms.dm.GetResourceProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(rp.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + args.ResourceS = indexes.Size() != 0 + } + //RouteSProfile Indexes + if args.RouteS { + cacheIDs[utils.CacheRouteFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheRouteFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + rp, e := adms.dm.GetRouteProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(rp.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + args.RouteS = indexes.Size() != 0 + } + //AttributeProfile Indexes + if args.AttributeS { + cacheIDs[utils.CacheAttributeFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheAttributeFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + attr, e := adms.dm.GetAttributeProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(attr.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + args.AttributeS = indexes.Size() != 0 + } + //ChargerProfile Indexes + if args.ChargerS { + cacheIDs[utils.CacheChargerFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheChargerFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + ch, e := adms.dm.GetChargerProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(ch.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + args.ChargerS = indexes.Size() != 0 + } + //AccountFilter Indexes + if args.AccountS { + cacheIDs[utils.CacheAccountsFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheAccountsFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + acnts, e := adms.dm.GetAccount(ctx, tnt, id) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(acnts.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + args.AccountS = indexes.Size() != 0 + } + //ActionFilter Indexes + if args.ActionS { + cacheIDs[utils.CacheActionProfilesFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheActionProfilesFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + act, e := adms.dm.GetActionProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(act.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + args.ActionS = indexes.Size() != 0 + } + // RateFilter Indexes + var ratePrf []string + if args.RateS { + cacheIDs[utils.CacheRateProfilesFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheRateProfilesFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + rtPrf, e := adms.dm.GetRateProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + ratePrf = append(ratePrf, utils.ConcatenatedKey(tnt, id)) + rtIds := make([]string, 0, len(rtPrf.Rates)) + for key := range rtPrf.Rates { + rtIds = append(rtIds, key) + } + cacheIDs[utils.CacheRateFilterIndexes] = rtIds + _, e = engine.ComputeIndexes(ctx, adms.dm, tnt, id, utils.CacheRateFilterIndexes, + &rtIds, transactionID, func(_, id, _ string) (*[]string, error) { + return utils.SliceStringPointer(slices.Clone(rtPrf.Rates[id].FilterIDs)), nil + }, nil) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(rtPrf.FilterIDs)), nil + }, nil); err != nil { + return utils.APIErrorHandler(err) + } + args.RateS = indexes.Size() != 0 + } + //DispatcherProfile Indexes + if args.DispatcherS { + cacheIDs[utils.CacheDispatcherFilterIndexes] = []string{utils.MetaAny} + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheDispatcherFilterIndexes, + nil, transactionID, func(tnt, id, grp string) (*[]string, error) { + dsp, e := adms.dm.GetDispatcherProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(dsp.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrDSPProfileNotFound { + return utils.APIErrorHandler(err) + } + args.DispatcherS = indexes.Size() != 0 + } + + //Now we move from tmpKey to the right key for each type + //ThresholdProfile Indexes + if args.ThresholdS { + if err = adms.dm.SetIndexes(ctx, utils.CacheThresholdFilterIndexes, tnt, nil, true, transactionID); err != nil { + return + } + } + //StatQueueProfile Indexes + if args.StatS { + if err = adms.dm.SetIndexes(ctx, utils.CacheStatFilterIndexes, tnt, nil, true, transactionID); err != nil { + return + } + } + //ResourceProfile Indexes + if args.ResourceS { + if err = adms.dm.SetIndexes(ctx, utils.CacheResourceFilterIndexes, tnt, nil, true, transactionID); err != nil { + return + } + } + //RouteProfile Indexes + if args.RouteS { + if err = adms.dm.SetIndexes(ctx, utils.CacheRouteFilterIndexes, tnt, nil, true, transactionID); err != nil { + return + } + } + //AttributeProfile Indexes + if args.AttributeS { + if err = adms.dm.SetIndexes(ctx, utils.CacheAttributeFilterIndexes, tnt, nil, true, transactionID); err != nil { + return + } + } + //ChargerProfile Indexes + if args.ChargerS { + if err = adms.dm.SetIndexes(ctx, utils.CacheChargerFilterIndexes, tnt, nil, true, transactionID); err != nil { + return + } + } + //AccountProfile Indexes + if args.AccountS { + if err = adms.dm.SetIndexes(ctx, utils.CacheAccountsFilterIndexes, tnt, nil, true, transactionID); err != nil { + return err + } + } + //ActionProfile Indexes + if args.ActionS { + if err = adms.dm.SetIndexes(ctx, utils.CacheActionProfilesFilterIndexes, tnt, nil, true, transactionID); err != nil { + return err + } + } + //RateProfile Indexes + if args.RateS { + if err = adms.dm.SetIndexes(ctx, utils.CacheRateProfilesFilterIndexes, tnt, nil, true, transactionID); err != nil { + return err + } + for _, tntId := range ratePrf { + if err = adms.dm.SetIndexes(ctx, utils.CacheRateFilterIndexes, tntId, nil, true, transactionID); err != nil { + return err + } + + } + } + //DispatcherProfile Indexes + if args.DispatcherS { + if err = adms.dm.SetIndexes(ctx, utils.CacheDispatcherFilterIndexes, tnt, nil, true, transactionID); err != nil { + return + } + } + //generate a load + //ID for CacheFilterIndexes and store it in database + loadIDs := make(map[string]int64) + timeNow := time.Now().UnixNano() + for idx := range cacheIDs { + loadIDs[idx] = timeNow + } + if err := adms.dm.SetLoadIDs(ctx, loadIDs); err != nil { + return utils.APIErrorHandler(err) + } + if err := adms.callCacheForComputeIndexes(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), + args.Tenant, cacheIDs, args.APIOpts); err != nil { + return err + } + *reply = utils.OK + return nil +} + +// ComputeFilterIndexIDs computes specific filter indexes +func (adms *AdminS) V1ComputeFilterIndexIDs(ctx *context.Context, args *utils.ArgsComputeFilterIndexIDs, reply *string) (err error) { + transactionID := utils.NonTransactional + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + var indexes utils.StringSet + cacheIDs := make(map[string][]string) + //ThresholdProfile Indexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheThresholdFilterIndexes, + &args.ThresholdIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + th, e := adms.dm.GetThresholdProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(th.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheThresholdFilterIndexes] = indexes.AsSlice() + } + //StatQueueProfile Indexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheStatFilterIndexes, + &args.StatIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + sq, e := adms.dm.GetStatQueueProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + cacheIDs[utils.CacheStatFilterIndexes] = []string{sq.ID} + return utils.SliceStringPointer(slices.Clone(sq.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheStatFilterIndexes] = indexes.AsSlice() + } + //ResourceProfile Indexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheResourceFilterIndexes, + &args.ResourceIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + rp, e := adms.dm.GetResourceProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + cacheIDs[utils.CacheResourceFilterIndexes] = []string{rp.ID} + return utils.SliceStringPointer(slices.Clone(rp.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheResourceFilterIndexes] = indexes.AsSlice() + } + //RouteProfile Indexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheRouteFilterIndexes, + &args.RouteIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + rp, e := adms.dm.GetRouteProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + cacheIDs[utils.CacheRouteFilterIndexes] = []string{rp.ID} + return utils.SliceStringPointer(slices.Clone(rp.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheRouteFilterIndexes] = indexes.AsSlice() + } + //AttributeProfile Indexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheAttributeFilterIndexes, + &args.AttributeIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + attr, e := adms.dm.GetAttributeProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(attr.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheAttributeFilterIndexes] = indexes.AsSlice() + } + //ChargerProfile Indexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheChargerFilterIndexes, + &args.ChargerIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + ch, e := adms.dm.GetChargerProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(ch.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheChargerFilterIndexes] = indexes.AsSlice() + } + //AccountIndexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheAccountsFilterIndexes, + &args.AccountIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + acc, e := adms.dm.GetAccount(ctx, tnt, id) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(acc.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheAccountsFilterIndexes] = indexes.AsSlice() + } + //ActionProfile Indexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheActionProfilesFilterIndexes, + &args.ActionProfileIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + act, e := adms.dm.GetActionProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(act.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheActionProfilesFilterIndexes] = indexes.AsSlice() + } + //RateProfile Indexes + var ratePrf []string + if _, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheRateProfilesFilterIndexes, + &args.RateProfileIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + rpr, e := adms.dm.GetRateProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + ratePrf = append(ratePrf, utils.ConcatenatedKey(tnt, id)) + rtIds := make([]string, 0, len(rpr.Rates)) + for key := range rpr.Rates { + rtIds = append(rtIds, key) + } + indexesRate, e := engine.ComputeIndexes(ctx, adms.dm, tnt, id, utils.CacheRateFilterIndexes, + &rtIds, transactionID, func(_, id, _ string) (*[]string, error) { + return utils.SliceStringPointer(slices.Clone(rpr.Rates[id].FilterIDs)), nil + }, nil) + if e != nil { + return nil, e + } + if indexesRate.Size() != 0 { + cacheIDs[utils.CacheRateFilterIndexes] = indexesRate.AsSlice() + } + return utils.SliceStringPointer(slices.Clone(rpr.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheRateProfilesFilterIndexes] = indexes.AsSlice() + } + //DispatcherProfile Indexes + if indexes, err = engine.ComputeIndexes(ctx, adms.dm, tnt, utils.EmptyString, utils.CacheDispatcherFilterIndexes, + &args.DispatcherIDs, transactionID, func(tnt, id, grp string) (*[]string, error) { + dsp, e := adms.dm.GetDispatcherProfile(ctx, tnt, id, true, false, utils.NonTransactional) + if e != nil { + return nil, e + } + return utils.SliceStringPointer(slices.Clone(dsp.FilterIDs)), nil + }, nil); err != nil && err != utils.ErrDSPProfileNotFound { + return utils.APIErrorHandler(err) + } + if indexes.Size() != 0 { + cacheIDs[utils.CacheDispatcherFilterIndexes] = indexes.AsSlice() + } + //generate a load + //ID for CacheFilterIndexes and store it in database + + loadIDs := make(map[string]int64) + timeNow := time.Now().UnixNano() + for idx := range cacheIDs { + loadIDs[idx] = timeNow + } + if err := adms.dm.SetLoadIDs(ctx, loadIDs); err != nil { + return utils.APIErrorHandler(err) + } + if err := adms.callCacheForComputeIndexes(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), + args.Tenant, cacheIDs, args.APIOpts); err != nil { + return err + } + *reply = utils.OK + return nil +} + +func (adms *AdminS) V1GetReverseFilterHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *map[string]*engine.ReverseFilterIHReply) (err error) { + objCaches := map[string]*ltcache.Cache{utils.CacheRateFilterIndexes: ltcache.NewCache(-1, 0, false, nil)} + for indxType := range utils.CacheIndexesToPrefix { + objCaches[indxType] = ltcache.NewCache(-1, 0, false, nil) + } + + *reply, err = engine.GetRevFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + objCaches, + ) + return +} + +func (adms *AdminS) V1GetThresholdsIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheThresholdFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetResourcesIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheResourceFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetStatsIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheStatFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetRoutesIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheRouteFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetAttributesIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheAttributeFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetChargersIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheChargerFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetDispatchersIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheDispatcherFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetRateProfilesIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheRateProfilesFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetActionsIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheActionProfilesFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetAccountsIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealth(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheAccountsFilterIndexes, + ) + if err != nil { + return err + } + *reply = *rp + return nil +} + +func (adms *AdminS) V1GetRateRatesIndexesHealth(ctx *context.Context, args *engine.IndexHealthArgs, reply *engine.FilterIHReply) error { + rp, err := engine.GetFltrIdxHealthForRateRates(ctx, adms.dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + ) + if err != nil { + return err + } + *reply = *rp + return nil +} diff --git a/admins/filters.go b/admins/filters.go new file mode 100644 index 000000000..d11caa494 --- /dev/null +++ b/admins/filters.go @@ -0,0 +1,210 @@ +/* +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 +*/ + +package admins + +import ( + "fmt" + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +func validateFilterRules(rules []*engine.FilterRule) error { + for _, rule := range rules { + if !rule.IsValid() { + return fmt.Errorf("there exists at least one filter rule that is not valid") + } + } + return nil +} + +// SetFilter add a new Filter +func (adms *AdminS) V1SetFilter(ctx *context.Context, arg *engine.FilterWithAPIOpts, reply *string) (err error) { + if missing := utils.MissingStructFields(arg.Filter, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if len(arg.Rules) == 0 { + return utils.NewErrMandatoryIeMissing("Filter Rules") + } + if err = validateFilterRules(arg.Rules); err != nil { + return utils.APIErrorHandler(err) + } + if arg.Tenant == utils.EmptyString { + arg.Tenant = adms.cfg.GeneralCfg().DefaultTenant + } + tntID := arg.TenantID() + argC := map[string][]string{utils.CacheFilters: {tntID}} + if fltr, err := adms.dm.GetFilter(ctx, arg.Filter.Tenant, arg.Filter.ID, true, false, utils.NonTransactional); err != nil { + if err != utils.ErrNotFound { + return utils.APIErrorHandler(err) + } + } else if argC, err = composeCacheArgsForFilter(adms.dm, ctx, fltr, fltr.Tenant, tntID, argC); err != nil { + return utils.APIErrorHandler(err) + } + if err := adms.dm.SetFilter(ctx, arg.Filter, true); err != nil { + return utils.APIErrorHandler(err) + } + if argC, err = composeCacheArgsForFilter(adms.dm, ctx, arg.Filter, arg.Filter.Tenant, tntID, argC); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheFilters and store it in database + if err := adms.dm.SetLoadIDs(ctx, + map[string]int64{utils.CacheFilters: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for Filter + if err := callCacheForFilter(adms.connMgr, adms.cfg.AdminSCfg().CachesConns, ctx, + utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), + adms.cfg.GeneralCfg().DefaultCaching, + arg.Tenant, argC, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return +} + +// GetFilter returns a Filter +func (adms *AdminS) V1GetFilter(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.Filter) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if fltr, err := adms.dm.GetFilter(ctx, tnt, arg.ID, true, true, utils.NonTransactional); err != nil { + return utils.APIErrorHandler(err) + } else { + *reply = *fltr + } + return nil +} + +// GetFilters returns a list of filters for a tenant +func (adms *AdminS) V1GetFilters(ctx *context.Context, args *utils.ArgsItemIDs, fltrs *[]*engine.Filter) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + var fltrIDs []string + if err = adms.V1GetFilterIDs(ctx, args, &fltrIDs); err != nil { + return + } + *fltrs = make([]*engine.Filter, 0, len(fltrIDs)) + for _, fltrID := range fltrIDs { + var fltr *engine.Filter + if fltr, err = adms.dm.GetFilter(ctx, tnt, fltrID, true, true, utils.NonTransactional); err != nil { + return utils.APIErrorHandler(err) + } + *fltrs = append(*fltrs, fltr) + } + return +} + +// GetFilterIDs returns list of Filter IDs registered for a tenant +func (adms *AdminS) V1GetFilterIDs(ctx *context.Context, args *utils.ArgsItemIDs, fltrIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.FilterPrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = adms.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *fltrIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// RemoveFilter remove a specific filter +func (adms *AdminS) V1RemoveFilter(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if err := adms.dm.RemoveFilter(ctx, tnt, arg.ID, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheFilters and store it in database + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheFilters: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for Filter + if err := callCacheForFilter(adms.connMgr, adms.cfg.AdminSCfg().CachesConns, ctx, + utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), + adms.cfg.GeneralCfg().DefaultCaching, + arg.Tenant, map[string][]string{utils.CacheFilters: {utils.ConcatenatedKey(tnt, arg.ID)}}, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// GetFiltersCount returns the total number of FilterIDs registered for a tenant +// returns ErrNotFound in case of 0 FilterIDs +func (admS *AdminS) V1GetFiltersCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.FilterPrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// FiltersMatch checks whether a set of filter IDs passes for the provided CGREvent +func (admS *AdminS) V1FiltersMatch(ctx *context.Context, args *engine.ArgsFiltersMatch, reply *bool) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + evDP := args.CGREvent.AsDataProvider() + var pass bool + if pass, err = admS.fltrS.Pass(ctx, tnt, args.FilterIDs, evDP); err != nil { + return + } else if pass { + *reply = true + } + return +} diff --git a/admins/libadmin.go b/admins/libadmin.go new file mode 100644 index 000000000..d4703f77a --- /dev/null +++ b/admins/libadmin.go @@ -0,0 +1,307 @@ +/* +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 +*/ + +package admins + +import ( + "strings" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// CallCache caching the item based on CacheOpt +func (admS *AdminS) CallCache(ctx *context.Context, cacheopt string, tnt, cacheID, itemID, groupID string, + filters *[]string, opts map[string]any) (err error) { + var reply, method string + var args any + switch utils.FirstNonEmpty(cacheopt, admS.cfg.GeneralCfg().DefaultCaching) { + case utils.MetaNone: + return + case utils.MetaReload: + method = utils.CacheSv1ReloadCache + var argCache map[string][]string + if argCache, err = admS.composeArgsReload(ctx, tnt, cacheID, itemID, filters); err != nil { + return + } + args = utils.NewAttrReloadCacheWithOptsFromMap(argCache, tnt, opts) + case utils.MetaLoad: + method = utils.CacheSv1LoadCache + var argCache map[string][]string + if argCache, err = admS.composeArgsReload(ctx, tnt, cacheID, itemID, filters); err != nil { + return + } + args = utils.NewAttrReloadCacheWithOptsFromMap(argCache, tnt, opts) + case utils.MetaRemove: + if groupID != utils.EmptyString { + method = utils.CacheSv1RemoveGroup + args = &utils.ArgsGetGroupWithAPIOpts{ + Tenant: tnt, + APIOpts: opts, + ArgsGetGroup: utils.ArgsGetGroup{ + CacheID: cacheID, + GroupID: groupID, + }, + } + break + } + method = utils.CacheSv1RemoveItems + var argCache map[string][]string + if argCache, err = admS.composeArgsReload(ctx, tnt, cacheID, itemID, filters); err != nil { + return + } + args = utils.NewAttrReloadCacheWithOptsFromMap(argCache, tnt, opts) + case utils.MetaClear: + cacheIDs := make([]string, 1, 3) + cacheIDs[0] = cacheID + // do not send a EmptyString if the item doesn't have indexes + if cIdx, has := utils.CacheInstanceToCacheIndex[cacheID]; has { + cacheIDs = append(cacheIDs, cIdx) + } + switch cacheID { // add the items to the cache reload + case utils.CacheThresholdProfiles: + cacheIDs = append(cacheIDs, utils.CacheThresholds) + case utils.CacheResourceProfiles: + cacheIDs = append(cacheIDs, utils.CacheResources) + case utils.CacheStatQueueProfiles: + cacheIDs = append(cacheIDs, utils.CacheStatQueues) + } + method = utils.CacheSv1Clear + args = &utils.AttrCacheIDsWithAPIOpts{ + Tenant: tnt, + CacheIDs: cacheIDs, + APIOpts: opts, + } + + } + return admS.connMgr.Call(ctx, admS.cfg.AdminSCfg().CachesConns, + method, args, &reply) +} + +// composeArgsReload add the ItemID to AttrReloadCache +// for a specific CacheID +func (admS *AdminS) composeArgsReload(ctx *context.Context, tnt, cacheID, itemID string, filterIDs *[]string) (argCache map[string][]string, err error) { + argCache = map[string][]string{cacheID: {itemID}} + switch cacheID { // add the items to the cache reload + case utils.CacheThresholdProfiles: + argCache[utils.CacheThresholds] = []string{itemID} + case utils.CacheResourceProfiles: + argCache[utils.CacheResources] = []string{itemID} + case utils.CacheStatQueueProfiles: + argCache[utils.CacheStatQueues] = []string{itemID} + } + if filterIDs == nil { // in case we remove a profile we do not need to reload the indexes + return + } + // populate the indexes + idxCacheID := utils.CacheInstanceToCacheIndex[cacheID] + if len(*filterIDs) == 0 { // in case we do not have any filters reload the *none filter indexes + indxID := utils.ConcatenatedKey(utils.MetaNone, utils.MetaAny, utils.MetaAny) + argCache[idxCacheID] = []string{utils.ConcatenatedKey(tnt, indxID)} + return + } + indxIDs := make([]string, 0, len(*filterIDs)) + for _, id := range *filterIDs { + var fltr *engine.Filter + if fltr, err = admS.dm.GetFilter(ctx, tnt, id, true, true, utils.NonTransactional); err != nil { + return + } + for _, flt := range fltr.Rules { + if !engine.FilterIndexTypes.Has(flt.Type) || + engine.IsDynamicDPPath(flt.Element) { + continue + } + isDyn := strings.HasPrefix(flt.Element, utils.DynamicDataPrefix) + for _, fldVal := range flt.Values { + if engine.IsDynamicDPPath(fldVal) { + continue + } + if isDyn { + if !strings.HasPrefix(fldVal, utils.DynamicDataPrefix) { + indxIDs = append(indxIDs, utils.ConcatenatedKey(flt.Type, flt.Element[1:], fldVal)) + } + } else if strings.HasPrefix(fldVal, utils.DynamicDataPrefix) { + indxIDs = append(indxIDs, utils.ConcatenatedKey(flt.Type, fldVal[1:], flt.Element)) + } + } + } + } + argCache[idxCacheID] = make([]string, len(indxIDs)) + for i, indxID := range indxIDs { + argCache[idxCacheID][i] = utils.ConcatenatedKey(tnt, indxID) + } + return +} + +// callCacheForIndexes will only call CacheClear because don't have access at ItemID +func (admS *AdminS) callCacheForRemoveIndexes(ctx *context.Context, cacheopt string, tnt, cacheID string, + itemIDs []string, opts map[string]any) (err error) { + var reply, method string + var args any = utils.NewAttrReloadCacheWithOptsFromMap(map[string][]string{cacheID: itemIDs}, tnt, opts) + switch utils.FirstNonEmpty(cacheopt, admS.cfg.GeneralCfg().DefaultCaching) { + case utils.MetaNone: + return + case utils.MetaReload: + method = utils.CacheSv1ReloadCache + case utils.MetaLoad: + method = utils.CacheSv1LoadCache + case utils.MetaRemove: + method = utils.CacheSv1RemoveItems + case utils.MetaClear: + method = utils.CacheSv1Clear + args = &utils.AttrCacheIDsWithAPIOpts{ + Tenant: tnt, + CacheIDs: []string{cacheID}, + APIOpts: opts, + } + } + return admS.connMgr.Call(ctx, admS.cfg.AdminSCfg().CachesConns, + method, args, &reply) +} + +func (admS *AdminS) callCacheForComputeIndexes(ctx *context.Context, cacheopt, tnt string, + cacheItems map[string][]string, opts map[string]any) (err error) { + var reply, method string + var args any = utils.NewAttrReloadCacheWithOptsFromMap(cacheItems, tnt, opts) + switch utils.FirstNonEmpty(cacheopt, admS.cfg.GeneralCfg().DefaultCaching) { + case utils.MetaNone: + return + case utils.MetaReload: + method = utils.CacheSv1ReloadCache + case utils.MetaLoad: + method = utils.CacheSv1LoadCache + case utils.MetaRemove: + method = utils.CacheSv1RemoveItems + case utils.MetaClear: + method = utils.CacheSv1Clear + cacheIDs := make([]string, 0, len(cacheItems)) + for idx := range cacheItems { + cacheIDs = append(cacheIDs, idx) + } + args = &utils.AttrCacheIDsWithAPIOpts{ + Tenant: tnt, + CacheIDs: cacheIDs, + APIOpts: opts, + } + } + return admS.connMgr.Call(ctx, admS.cfg.AdminSCfg().CachesConns, + method, args, &reply) +} + +// callCacheRevDestinations used for reverse destination, loadIDs and indexes replication +func (admS *AdminS) callCacheMultiple(ctx *context.Context, cacheopt, tnt, cacheID string, itemIDs []string, opts map[string]any) (err error) { + if len(itemIDs) == 0 { + return + } + var reply, method string + var args any + switch utils.FirstNonEmpty(cacheopt, admS.cfg.GeneralCfg().DefaultCaching) { + case utils.MetaNone: + return + case utils.MetaReload: + method = utils.CacheSv1ReloadCache + args = utils.NewAttrReloadCacheWithOptsFromMap(map[string][]string{cacheID: itemIDs}, tnt, opts) + case utils.MetaLoad: + method = utils.CacheSv1LoadCache + args = utils.NewAttrReloadCacheWithOptsFromMap(map[string][]string{cacheID: itemIDs}, tnt, opts) + case utils.MetaRemove: + method = utils.CacheSv1RemoveItems + args = utils.NewAttrReloadCacheWithOptsFromMap(map[string][]string{cacheID: itemIDs}, tnt, opts) + case utils.MetaClear: + method = utils.CacheSv1Clear + args = &utils.AttrCacheIDsWithAPIOpts{ + Tenant: tnt, + CacheIDs: []string{cacheID}, + APIOpts: opts, + } + } + return admS.connMgr.Call(ctx, admS.cfg.AdminSCfg().CachesConns, + method, args, &reply) +} + +func composeCacheArgsForFilter(dm *engine.DataManager, ctx *context.Context, fltr *engine.Filter, tnt, tntID string, args map[string][]string) (_ map[string][]string, err error) { + indxIDs := make([]string, 0, len(fltr.Rules)) + for _, flt := range fltr.Rules { + if !engine.FilterIndexTypes.Has(flt.Type) || + engine.IsDynamicDPPath(flt.Element) { + continue + } + isDyn := strings.HasPrefix(flt.Element, utils.DynamicDataPrefix) + for _, fldVal := range flt.Values { + if engine.IsDynamicDPPath(fldVal) { + continue + } + if isDyn { + if !strings.HasPrefix(fldVal, utils.DynamicDataPrefix) { + indxIDs = append(indxIDs, utils.ConcatenatedKey(flt.Type, flt.Element[1:], fldVal)) + } + } else if strings.HasPrefix(fldVal, utils.DynamicDataPrefix) { + indxIDs = append(indxIDs, utils.ConcatenatedKey(flt.Type, fldVal[1:], flt.Element)) + } + } + } + if len(indxIDs) == 0 { // no index + return args, nil + } + + var rcvIndx map[string]utils.StringSet + if rcvIndx, err = dm.GetIndexes(ctx, utils.CacheReverseFilterIndexes, tntID, + utils.EmptyString, utils.EmptyString, true, true); err != nil && err != utils.ErrNotFound { // error when geting the revers + return + } + if err == utils.ErrNotFound || len(rcvIndx) == 0 { // no reverse index for this filter + return args, nil + } + + for k := range rcvIndx { + for _, indx := range indxIDs { + args[k] = append(args[k], utils.ConcatenatedKey(tnt, indx)) + } + } + return args, nil +} + +// callCacheForFilter will call the cache for filter +func callCacheForFilter(connMgr *engine.ConnManager, cacheConns []string, ctx *context.Context, cacheopt, dftCache, tnt string, + argC map[string][]string, opts map[string]any) (err error) { + var reply, method string + var args any = utils.NewAttrReloadCacheWithOptsFromMap(argC, tnt, opts) + switch utils.FirstNonEmpty(cacheopt, dftCache) { + case utils.MetaNone: + return + case utils.MetaReload: + method = utils.CacheSv1ReloadCache + case utils.MetaLoad: + method = utils.CacheSv1LoadCache + case utils.MetaRemove: + method = utils.CacheSv1RemoveItems + case utils.MetaClear: + cacheIDs := make([]string, 0, len(argC)) + for k := range argC { + cacheIDs = append(cacheIDs, k) + } + method = utils.CacheSv1Clear + args = &utils.AttrCacheIDsWithAPIOpts{ + Tenant: tnt, + CacheIDs: cacheIDs, + APIOpts: opts, + } + } + return connMgr.Call(ctx, cacheConns, method, args, &reply) +} diff --git a/admins/rates.go b/admins/rates.go new file mode 100644 index 000000000..9197f20c7 --- /dev/null +++ b/admins/rates.go @@ -0,0 +1,292 @@ +/* +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 +*/ + +package admins + +import ( + "sort" + "strings" + "time" + + "github.com/cgrates/birpc/context" + + "github.com/cgrates/cgrates/utils" +) + +// GetRateProfile returns a Rate Profile based on tenant and id +func (admS *AdminS) V1GetRateProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *utils.RateProfile) (err error) { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var rPrf *utils.RateProfile + rPrf, err = admS.dm.GetRateProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional) + if err != nil { + if err.Error() != utils.ErrNotFound.Error() { + err = utils.NewErrServerError(err) + } + return + } + rateIDs := make([]string, 0, len(rPrf.Rates)) + prefix := utils.IfaceAsString(arg.APIOpts[utils.ItemsPrefixOpt]) + for rateID := range rPrf.Rates { + if strings.HasPrefix(rateID, prefix) { + rateIDs = append(rateIDs, rateID) + } + } + sort.Strings(rateIDs) + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(arg.APIOpts); err != nil { + return + } + rateIDs, err = utils.Paginate(rateIDs, limit, offset, maxItems) + if err != nil { + return + } + paginatedRatePrf := &utils.RateProfile{ + Tenant: rPrf.Tenant, + ID: rPrf.ID, + FilterIDs: rPrf.FilterIDs, + Weights: rPrf.Weights, + MinCost: rPrf.MinCost, + MaxCost: rPrf.MaxCost, + MaxCostStrategy: rPrf.MaxCostStrategy, + } + paginatedRatePrf.Rates = make(map[string]*utils.Rate) + for _, rateID := range rateIDs { + paginatedRatePrf.Rates[rateID] = rPrf.Rates[rateID].Clone() + } + *reply = *paginatedRatePrf + return +} + +// GetRateProfile returns the rates of a profile based on their profile. Those rates will be returned back by matching a prefix. +func (admS *AdminS) V1GetRateProfileRates(ctx *context.Context, args *utils.ArgsSubItemIDs, reply *[]*utils.Rate) (err error) { + if missing := utils.MissingStructFields(args, []string{utils.ProfileID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + _, rates, err := admS.dm.GetRateProfileRates(ctx, args, false) + if err != nil { + return + } + if len(rates) == 0 { + return utils.ErrNotFound + } + *reply = rates + return +} + +// GetRateProfileIDs returns a list of rate profile IDs registered for a tenant +func (admS *AdminS) V1GetRateProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, ratePrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.RateProfilePrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *ratePrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetRateProfileRateIDs returns a list of rates from a specific RateProfile registered for a tenant. RateIDs are returned back by matching a pattern given by ItemPrefix. If the ItemPrefix is not there, it will be returned all RateIDs. +func (admS *AdminS) V1GetRateProfileRateIDs(ctx *context.Context, args *utils.ArgsSubItemIDs, rateIDs *[]string) (err error) { + if missing := utils.MissingStructFields(args, []string{utils.ProfileID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + var ids []string + ids, _, err = admS.dm.GetRateProfileRates(ctx, args, true) + if err != nil { + return + } + if len(ids) == 0 { + return utils.ErrNotFound + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *rateIDs, err = utils.Paginate(ids, limit, offset, maxItems) + return +} + +// GetRateProfiles returns a list of rate profiles registered for a tenant +func (admS *AdminS) V1GetRateProfiles(ctx *context.Context, args *utils.ArgsItemIDs, ratePrfs *[]*utils.RateProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var ratePrfIDs []string + if err = admS.V1GetRateProfileIDs(ctx, args, &ratePrfIDs); err != nil { + return + } + *ratePrfs = make([]*utils.RateProfile, 0, len(ratePrfIDs)) + for _, ratePrfID := range ratePrfIDs { + var ratePrf *utils.RateProfile + ratePrf, err = admS.dm.GetRateProfile(ctx, tnt, ratePrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *ratePrfs = append(*ratePrfs, ratePrf) + } + return +} + +// GetRateProfilesCount returns the total number of RateProfileIDs registered for a tenant +// returns ErrNotFound in case of 0 RateProfileIDs +func (admS *AdminS) V1GetRateProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.RateProfilePrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// GetRateProfileRatesCount count the rates from a specific RateProfile registered for a tenant. The number of rates is returned back by matching a pattern given by ItemPrefix. If the ItemPrefix is not there, it will be counted all the rates. +func (admS *AdminS) V1GetRateProfileRatesCount(ctx *context.Context, args *utils.ArgsSubItemIDs, countIDs *int) (err error) { + if missing := utils.MissingStructFields(args, []string{utils.ProfileID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + + var ids []string + ids, _, err = admS.dm.GetRateProfileRates(ctx, args, true) + if err != nil { + return + } + if len(ids) == 0 { + return utils.ErrNotFound + } + *countIDs = len(ids) + return +} + +// SetRateProfile add/update a new Rate Profile +func (admS *AdminS) V1SetRateProfile(ctx *context.Context, args *utils.APIRateProfile, reply *string) (err error) { + if missing := utils.MissingStructFields(args, []string{utils.ID, utils.Rates}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = admS.cfg.GeneralCfg().DefaultTenant + } + // check if we want to overwrite our profile already existing in database + var optOverwrite bool + if _, has := args.APIOpts[utils.MetaRateSOverwrite]; has { + optOverwrite, err = utils.IfaceAsBool(args.APIOpts[utils.MetaRateSOverwrite]) + if err != nil { + return + } + } + if err := admS.dm.SetRateProfile(ctx, args.RateProfile, optOverwrite, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheRateProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheRateProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), args.Tenant, utils.CacheRateProfiles, + args.TenantID(), utils.EmptyString, &args.FilterIDs, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveRateProfileRates removes the rates from the Rate Profile +func (admS *AdminS) V1RemoveRateProfileRates(ctx *context.Context, args *utils.RemoveRPrfRates, reply *string) (err error) { + if missing := utils.MissingStructFields(args, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.RemoveRateProfileRates(ctx, tnt, args.ID, &args.RateIDs, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheRateProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheRateProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), tnt, utils.CacheRateProfiles, + utils.ConcatenatedKey(tnt, args.ID), utils.EmptyString, nil, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveRateProfile remove a specific Rate Profile specified by tenant and id +func (admS *AdminS) V1RemoveRateProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + if err := admS.dm.RemoveRateProfile(ctx, tnt, arg.ID, + true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheAttributeProfiles and store it in database + if err := admS.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheRateProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + if err := admS.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), tnt, utils.CacheRateProfiles, + utils.ConcatenatedKey(tnt, arg.ID), utils.EmptyString, nil, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/resources.go b/admins/resources.go new file mode 100644 index 000000000..fbc5a51b5 --- /dev/null +++ b/admins/resources.go @@ -0,0 +1,168 @@ +/* +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 +*/ + +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetResourceProfile returns a resource configuration +func (adms *AdminS) V1GetResourceProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.ResourceProfile) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if rcfg, err := adms.dm.GetResourceProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional); err != nil { + return utils.APIErrorHandler(err) + } else { + *reply = *rcfg + } + return nil +} + +// GetResourceProfileIDs returns list of resourceProfile IDs registered for a tenant +func (adms *AdminS) V1GetResourceProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, rsPrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.ResourceProfilesPrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = adms.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *rsPrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetResourceProfiles returns a list of resource profiles registered for a tenant +func (admS *AdminS) V1GetResourceProfiles(ctx *context.Context, args *utils.ArgsItemIDs, rsPrfs *[]*engine.ResourceProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var rsPrfIDs []string + if err = admS.V1GetResourceProfileIDs(ctx, args, &rsPrfIDs); err != nil { + return + } + *rsPrfs = make([]*engine.ResourceProfile, 0, len(rsPrfIDs)) + for _, rsPrfID := range rsPrfIDs { + var rsPrf *engine.ResourceProfile + rsPrf, err = admS.dm.GetResourceProfile(ctx, tnt, rsPrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *rsPrfs = append(*rsPrfs, rsPrf) + } + return +} + +// GetResourceProfilesCount returns the total number of ResourceProfileIDs registered for a tenant +// returns ErrNotFound in case of 0 ResourceProfileIDs +func (admS *AdminS) V1GetResourceProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.ResourceProfilesPrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// SetResourceProfile adds a new resource configuration +func (adms *AdminS) V1SetResourceProfile(ctx *context.Context, arg *engine.ResourceProfileWithAPIOpts, reply *string) (err error) { + if missing := utils.MissingStructFields(arg.ResourceProfile, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if arg.Tenant == utils.EmptyString { + arg.Tenant = adms.cfg.GeneralCfg().DefaultTenant + } + if err = adms.dm.SetResourceProfile(ctx, arg.ResourceProfile, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheResourceProfiles and CacheResources and store it in database + //make 1 insert for both ResourceProfile and Resources instead of 2 + loadID := time.Now().UnixNano() + if err = adms.dm.SetLoadIDs(ctx, + map[string]int64{utils.CacheResourceProfiles: loadID, + utils.CacheResources: loadID}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for ResourceProfile + if err = adms.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), arg.Tenant, utils.CacheResourceProfiles, + arg.TenantID(), utils.EmptyString, &arg.FilterIDs, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveResourceProfile remove a specific resource configuration +func (adms *AdminS) V1RemoveResourceProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if err := adms.dm.RemoveResourceProfile(ctx, tnt, arg.ID, true); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for ResourceProfile + if err := adms.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), tnt, utils.CacheResourceProfiles, + utils.ConcatenatedKey(tnt, arg.ID), utils.EmptyString, nil, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheResourceProfiles and CacheResources and store it in database + //make 1 insert for both ResourceProfile and Resources instead of 2 + loadID := time.Now().UnixNano() + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheResourceProfiles: loadID, utils.CacheResources: loadID}); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/routes.go b/admins/routes.go new file mode 100644 index 000000000..6a12da3f8 --- /dev/null +++ b/admins/routes.go @@ -0,0 +1,165 @@ +/* +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 +*/ + +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetRouteProfile returns a Route configuration +func (adms *AdminS) V1GetRouteProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.RouteProfile) error { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if rp, err := adms.dm.GetRouteProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional); err != nil { + return utils.APIErrorHandler(err) + } else { + *reply = *rp + } + return nil +} + +// GetRouteProfileIDs returns list of routeProfile IDs registered for a tenant +func (adms *AdminS) V1GetRouteProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, routeProfileIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.RouteProfilePrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = adms.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *routeProfileIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetRouteProfiles returns a list of route profiles registered for a tenant +func (admS *AdminS) V1GetRouteProfiles(ctx *context.Context, args *utils.ArgsItemIDs, rouPrfs *[]*engine.RouteProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var rouPrfIDs []string + if err = admS.V1GetRouteProfileIDs(ctx, args, &rouPrfIDs); err != nil { + return + } + *rouPrfs = make([]*engine.RouteProfile, 0, len(rouPrfIDs)) + for _, rouPrfID := range rouPrfIDs { + var rouPrf *engine.RouteProfile + rouPrf, err = admS.dm.GetRouteProfile(ctx, tnt, rouPrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *rouPrfs = append(*rouPrfs, rouPrf) + } + return +} + +// GetRouteProfilesCount sets in reply var the total number of RouteProfileIDs registered for the received tenant +// returns ErrNotFound in case of 0 RouteProfileIDs +func (adms *AdminS) V1GetRouteProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.RouteProfilePrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = adms.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// SetRouteProfile add a new Route configuration +func (adms *AdminS) V1SetRouteProfile(ctx *context.Context, args *engine.RouteProfileWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(args.RouteProfile, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = adms.cfg.GeneralCfg().DefaultTenant + } + if len(args.Routes) == 0 { + return utils.NewErrMandatoryIeMissing("Routes") + } + if err := adms.dm.SetRouteProfile(ctx, args.RouteProfile, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheRouteProfiles and store it in database + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheRouteProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for SupplierProfile + if err := adms.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), args.Tenant, utils.CacheRouteProfiles, + args.TenantID(), utils.EmptyString, &args.FilterIDs, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveRouteProfile remove a specific Route configuration +func (adms *AdminS) V1RemoveRouteProfile(ctx *context.Context, args *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(args, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if err := adms.dm.RemoveRouteProfile(ctx, tnt, args.ID, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheRouteProfiles and store it in database + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheRouteProfiles: time.Now().UnixNano()}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for SupplierProfile + if err := adms.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), tnt, utils.CacheRouteProfiles, + utils.ConcatenatedKey(tnt, args.ID), utils.EmptyString, nil, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/stats.go b/admins/stats.go new file mode 100644 index 000000000..d72a1fa7d --- /dev/null +++ b/admins/stats.go @@ -0,0 +1,166 @@ +/* +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 +*/ +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetStatQueueProfile returns a StatQueue profile +func (adms *AdminS) V1GetStatQueueProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.StatQueueProfile) (err error) { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + sCfg, err := adms.dm.GetStatQueueProfile(ctx, tnt, arg.ID, + true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *reply = *sCfg + return +} + +// GetStatQueueProfileIDs returns list of statQueueProfile IDs registered for a tenant +func (adms *AdminS) V1GetStatQueueProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, stsPrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.StatQueueProfilePrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = adms.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *stsPrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetStatQueueProfiles returns a list of stats profiles registered for a tenant +func (admS *AdminS) V1GetStatQueueProfiles(ctx *context.Context, args *utils.ArgsItemIDs, sqPrfs *[]*engine.StatQueueProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var sqPrfIDs []string + if err = admS.V1GetStatQueueProfileIDs(ctx, args, &sqPrfIDs); err != nil { + return + } + *sqPrfs = make([]*engine.StatQueueProfile, 0, len(sqPrfIDs)) + for _, sqPrfID := range sqPrfIDs { + var sqPrf *engine.StatQueueProfile + sqPrf, err = admS.dm.GetStatQueueProfile(ctx, tnt, sqPrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *sqPrfs = append(*sqPrfs, sqPrf) + } + return +} + +// GetStatQueueProfilesCount returns the total number of StatQueueProfileIDs registered for a tenant +// returns ErrNotFound in case of 0 StatQueueProfileIDs +func (admS *AdminS) V1GetStatQueueProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.StatQueueProfilePrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = admS.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// SetStatQueueProfile alters/creates a StatQueueProfile +func (adms *AdminS) V1SetStatQueueProfile(ctx *context.Context, arg *engine.StatQueueProfileWithAPIOpts, reply *string) (err error) { + if missing := utils.MissingStructFields(arg.StatQueueProfile, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if arg.Tenant == utils.EmptyString { + arg.Tenant = adms.cfg.GeneralCfg().DefaultTenant + } + if err = adms.dm.SetStatQueueProfile(ctx, arg.StatQueueProfile, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheStatQueueProfiles and CacheStatQueues and store it in database + //make 1 insert for both StatQueueProfile and StatQueue instead of 2 + loadID := time.Now().UnixNano() + if err = adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheStatQueueProfiles: loadID, utils.CacheStatQueues: loadID}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for StatQueueProfile + if err = adms.CallCache(ctx, utils.IfaceAsString(arg.APIOpts[utils.MetaCache]), arg.Tenant, utils.CacheStatQueueProfiles, + arg.TenantID(), utils.EmptyString, &arg.FilterIDs, arg.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveStatQueueProfile remove a specific stat configuration +func (adms *AdminS) V1RemoveStatQueueProfile(ctx *context.Context, args *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(args, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if err := adms.dm.RemoveStatQueueProfile(ctx, tnt, args.ID, true); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for StatQueueProfile + if err := adms.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), tnt, utils.CacheStatQueueProfiles, + utils.ConcatenatedKey(tnt, args.ID), utils.EmptyString, nil, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheStatQueueProfiles and CacheStatQueues and store it in database + //make 1 insert for both StatQueueProfile and StatQueue instead of 2 + loadID := time.Now().UnixNano() + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheStatQueueProfiles: loadID, utils.CacheStatQueues: loadID}); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} diff --git a/admins/thresholds.go b/admins/thresholds.go new file mode 100644 index 000000000..f59c168a2 --- /dev/null +++ b/admins/thresholds.go @@ -0,0 +1,166 @@ +/* +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 +*/ + +package admins + +import ( + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// GetThresholdProfile returns a Threshold Profile +func (adms *AdminS) V1GetThresholdProfile(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *engine.ThresholdProfile) (err error) { + if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := arg.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + th, err := adms.dm.GetThresholdProfile(ctx, tnt, arg.ID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *reply = *th + return +} + +// GetThresholdProfileIDs returns list of thresholdProfile IDs registered for a tenant +func (adms *AdminS) V1GetThresholdProfileIDs(ctx *context.Context, args *utils.ArgsItemIDs, thPrfIDs *[]string) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.ThresholdProfilePrefix + tnt + utils.ConcatenatedKeySep + lenPrfx := len(prfx) + prfx += args.ItemsPrefix + var keys []string + if keys, err = adms.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return + } + if len(keys) == 0 { + return utils.ErrNotFound + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[lenPrfx:] + } + var limit, offset, maxItems int + if limit, offset, maxItems, err = utils.GetPaginateOpts(args.APIOpts); err != nil { + return + } + *thPrfIDs, err = utils.Paginate(retIDs, limit, offset, maxItems) + return +} + +// GetThresholdProfiles returns a list of threshold profiles registered for a tenant +func (admS *AdminS) V1GetThresholdProfiles(ctx *context.Context, args *utils.ArgsItemIDs, thdPrfs *[]*engine.ThresholdProfile) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = admS.cfg.GeneralCfg().DefaultTenant + } + var thdPrfIDs []string + if err = admS.V1GetThresholdProfileIDs(ctx, args, &thdPrfIDs); err != nil { + return + } + *thdPrfs = make([]*engine.ThresholdProfile, 0, len(thdPrfIDs)) + for _, thdPrfID := range thdPrfIDs { + var thdPrf *engine.ThresholdProfile + thdPrf, err = admS.dm.GetThresholdProfile(ctx, tnt, thdPrfID, true, true, utils.NonTransactional) + if err != nil { + return utils.APIErrorHandler(err) + } + *thdPrfs = append(*thdPrfs, thdPrf) + } + return +} + +// GetThresholdProfilesCount sets in reply var the total number of ThresholdProfileIDs registered for the received tenant +// returns ErrNotFound in case of 0 ThresholdProfileIDs +func (adms *AdminS) V1GetThresholdProfilesCount(ctx *context.Context, args *utils.ArgsItemIDs, reply *int) (err error) { + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + prfx := utils.ThresholdProfilePrefix + tnt + utils.ConcatenatedKeySep + args.ItemsPrefix + var keys []string + if keys, err = adms.dm.DataDB().GetKeysForPrefix(ctx, prfx); err != nil { + return err + } + if len(keys) == 0 { + return utils.ErrNotFound + } + *reply = len(keys) + return +} + +// SetThresholdProfile alters/creates a ThresholdProfile +func (adms *AdminS) V1SetThresholdProfile(ctx *context.Context, args *engine.ThresholdProfileWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(args.ThresholdProfile, []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if args.Tenant == utils.EmptyString { + args.Tenant = adms.cfg.GeneralCfg().DefaultTenant + } + if err := adms.dm.SetThresholdProfile(ctx, args.ThresholdProfile, true); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheThresholdProfiles and CacheThresholds and store it in database + //make 1 insert for both ThresholdProfile and Threshold instead of 2 + loadID := time.Now().UnixNano() + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheThresholdProfiles: loadID, utils.CacheThresholds: loadID}); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for ThresholdProfile and Threshold + if err := adms.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), args.Tenant, utils.CacheThresholdProfiles, + args.TenantID(), utils.EmptyString, &args.FilterIDs, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +} + +// RemoveThresholdProfile removes a specific Threshold Profile +func (adms *AdminS) V1RemoveThresholdProfile(ctx *context.Context, args *utils.TenantIDWithAPIOpts, reply *string) error { + if missing := utils.MissingStructFields(args, []string{utils.ID}); len(missing) != 0 { //Params missing + return utils.NewErrMandatoryIeMissing(missing...) + } + tnt := args.Tenant + if tnt == utils.EmptyString { + tnt = adms.cfg.GeneralCfg().DefaultTenant + } + if err := adms.dm.RemoveThresholdProfile(ctx, tnt, args.ID, true); err != nil { + return utils.APIErrorHandler(err) + } + //handle caching for ThresholdProfile + if err := adms.CallCache(ctx, utils.IfaceAsString(args.APIOpts[utils.MetaCache]), tnt, utils.CacheThresholdProfiles, + utils.ConcatenatedKey(tnt, args.ID), utils.EmptyString, nil, args.APIOpts); err != nil { + return utils.APIErrorHandler(err) + } + //generate a loadID for CacheThresholdProfiles and CacheThresholds and store it in database + //make 1 insert for both ThresholdProfile and Threshold instead of 2 + loadID := time.Now().UnixNano() + if err := adms.dm.SetLoadIDs(ctx, map[string]int64{utils.CacheThresholdProfiles: loadID, utils.CacheThresholds: loadID}); err != nil { + return utils.APIErrorHandler(err) + } + *reply = utils.OK + return nil +}