From d78f34bdc569e32d361f9b4e7f64204db2265d4d Mon Sep 17 00:00:00 2001 From: arberkatellari Date: Tue, 4 Nov 2025 10:36:03 +0200 Subject: [PATCH] Make Action Profiles storable in MySQL and Postgres --- apis/actions_it_test.go | 2 +- apis/analyzer_it_test.go | 2 +- config/config_defaults.go | 4 +- config/configsanity.go | 2 +- .../samples/apis_actions_mysql/cgrates.json | 3 +- .../apis_actions_postgres/cgrates.json | 3 +- .../samples/apis_actions_redis/cgrates.json | 55 +++++++++ data/conf/samples/tutmysql/cgrates.json | 2 +- data/conf/samples/tutpostgres/cgrates.json | 2 +- data/storage/mysql/create_db_tables.sql | 13 +- data/storage/postgres/create_db_tables.sql | 11 ++ engine/models.go | 11 ++ engine/storage_sql.go | 63 +++++++--- general_tests/dynamic_thresholds_it_test.go | 40 +++++- utils/actions.go | 114 ++++++++++++++++++ utils/consts.go | 43 +++---- utils/ips.go | 10 +- utils/stringset.go | 19 +++ 18 files changed, 346 insertions(+), 53 deletions(-) create mode 100644 data/conf/samples/apis_actions_redis/cgrates.json diff --git a/apis/actions_it_test.go b/apis/actions_it_test.go index e8c240d98..c3aea0995 100644 --- a/apis/actions_it_test.go +++ b/apis/actions_it_test.go @@ -104,7 +104,7 @@ func TestActionsIT(t *testing.T) { case utils.MetaMongo: actConfigDIR = "apis_actions_mongo" case utils.MetaRedis: - t.SkipNow() + actConfigDIR = "apis_actions_redis" case utils.MetaMySQL: actConfigDIR = "apis_actions_mysql" case utils.MetaPostgres: diff --git a/apis/analyzer_it_test.go b/apis/analyzer_it_test.go index 53b16ff55..b99764a90 100644 --- a/apis/analyzer_it_test.go +++ b/apis/analyzer_it_test.go @@ -62,7 +62,7 @@ var ( func TestAnalyzerSIT(t *testing.T) { switch *utils.DBType { - case utils.MetaInternal, utils.MetaMySQL, utils.MetaPostgres: + case utils.MetaInternal, utils.MetaRedis, utils.MetaMySQL, utils.MetaPostgres: t.SkipNow() case utils.MetaMongo: anzCfgPath = path.Join(*utils.DataDir, "conf", "samples", "analyzers") diff --git a/config/config_defaults.go b/config/config_defaults.go index 2932883f3..98b68a311 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -129,7 +129,8 @@ const CGRATES_CFG_JSON = ` "*accounts": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, "*ip_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, "*ip_allocations": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, - + "*action_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, + // compatible db types: <*internal|*redis|*mongo> "*actions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, "*resource_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, @@ -143,7 +144,6 @@ const CGRATES_CFG_JSON = ` "*attribute_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, "*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, "*rate_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, - "*action_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, "*load_ids": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, "*versions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"}, "*resource_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "*default"}, diff --git a/config/configsanity.go b/config/configsanity.go index 3692bcf42..f2ac78afe 100644 --- a/config/configsanity.go +++ b/config/configsanity.go @@ -1061,7 +1061,7 @@ func (cfg *CGRConfig) checkConfigSanity() error { utils.Internal, utils.Redis, utils.Mongo} if item != utils.MetaAccounts && item != utils.MetaIPProfiles && - item != utils.MetaIPAllocations { + item != utils.MetaIPAllocations && item != utils.MetaActionProfiles { if item == utils.MetaCDRs { if !slices.Contains(storDBTypes, cfg.dbCfg.DBConns[val.DBConn].Type) { return fmt.Errorf("<%s> db item can only be of types <%v>, got <%s>", item, diff --git a/data/conf/samples/apis_actions_mysql/cgrates.json b/data/conf/samples/apis_actions_mysql/cgrates.json index fce433887..5c463d282 100644 --- a/data/conf/samples/apis_actions_mysql/cgrates.json +++ b/data/conf/samples/apis_actions_mysql/cgrates.json @@ -21,7 +21,8 @@ }, }, "items": { - "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"} + "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}, + "*action_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}, } }, diff --git a/data/conf/samples/apis_actions_postgres/cgrates.json b/data/conf/samples/apis_actions_postgres/cgrates.json index 5cdc65400..699d6b0ba 100644 --- a/data/conf/samples/apis_actions_postgres/cgrates.json +++ b/data/conf/samples/apis_actions_postgres/cgrates.json @@ -23,7 +23,8 @@ } }, "items": { - "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"} + "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}, + "*action_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}, } }, diff --git a/data/conf/samples/apis_actions_redis/cgrates.json b/data/conf/samples/apis_actions_redis/cgrates.json new file mode 100644 index 000000000..fce433887 --- /dev/null +++ b/data/conf/samples/apis_actions_redis/cgrates.json @@ -0,0 +1,55 @@ +{ + +"logger": { + "level": 7 +}, + +"db": { + "db_conns": { + "*default": { + "db_type": "redis", + "db_port": 6379, + "db_name": "10" + }, + "StorDB": { // The id of the DB connection + "db_type": "mysql", // db type: + "db_host": "127.0.0.1", // the host to connect to + "db_port": 3306, // db port to reach the database + "db_name": "cgrates", // db database name to connect to + "db_user": "cgrates", // username to use when connecting to the database + "db_password": "CGRateS.org" // password to use when connecting to the database + }, + }, + "items": { + "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"} + } +}, + +"actions": { + "enabled": true, + "thresholds_conns": ["*internal"], + "stats_conns": ["*internal"], + "accounts_conns": ["*internal"] +}, + +"accounts": { + "enabled": true +}, + +"stats": { + "enabled": true, + "store_interval": "-1", + "thresholds_conns": ["*internal"] +}, + +"thresholds": { + "enabled": true, + "store_interval": "-1", + "actions_conns": ["*internal"] +}, + +"admins": { + "enabled": true +} + +} \ No newline at end of file diff --git a/data/conf/samples/tutmysql/cgrates.json b/data/conf/samples/tutmysql/cgrates.json index 31b7a0631..7e832905f 100644 --- a/data/conf/samples/tutmysql/cgrates.json +++ b/data/conf/samples/tutmysql/cgrates.json @@ -36,7 +36,7 @@ }, }, "items": { - "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"} + "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}, } }, diff --git a/data/conf/samples/tutpostgres/cgrates.json b/data/conf/samples/tutpostgres/cgrates.json index c8e6d80d9..fe2cdd0fe 100644 --- a/data/conf/samples/tutpostgres/cgrates.json +++ b/data/conf/samples/tutpostgres/cgrates.json @@ -36,7 +36,7 @@ }, "items": { "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}, - "*accounts": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"} + "*accounts": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}, } }, diff --git a/data/storage/mysql/create_db_tables.sql b/data/storage/mysql/create_db_tables.sql index bf3bc3db7..7ad05d027 100644 --- a/data/storage/mysql/create_db_tables.sql +++ b/data/storage/mysql/create_db_tables.sql @@ -33,4 +33,15 @@ CREATE TABLE ip_allocations ( PRIMARY KEY (`pk`), UNIQUE KEY unique_tenant_id (`tenant`, `id`) ); -CREATE UNIQUE INDEX ip_allocations_idx ON ip_allocations (`id`); \ No newline at end of file +CREATE UNIQUE INDEX ip_allocations_idx ON ip_allocations (`id`); + +DROP TABLE IF EXISTS action_profiles; +CREATE TABLE action_profiles ( + `pk` int(11) NOT NULL AUTO_INCREMENT, + `tenant` VARCHAR(40) NOT NULL, + `id` VARCHAR(64) NOT NULL, + `action_profile` JSON NOT NULL, + PRIMARY KEY (`pk`), + UNIQUE KEY unique_tenant_id (`tenant`, `id`) +); +CREATE UNIQUE INDEX action_profiles_idx ON action_profiles (`id`); \ No newline at end of file diff --git a/data/storage/postgres/create_db_tables.sql b/data/storage/postgres/create_db_tables.sql index 1dd5ba027..a4e9183d8 100644 --- a/data/storage/postgres/create_db_tables.sql +++ b/data/storage/postgres/create_db_tables.sql @@ -32,3 +32,14 @@ CREATE TABLE ip_allocations ( UNIQUE (tenant, id) ); CREATE UNIQUE INDEX ip_allocations_idx ON ip_allocations ("id"); + + +DROP TABLE IF EXISTS action_profiles; +CREATE TABLE action_profiles ( + pk SERIAL PRIMARY KEY, + tenant VARCHAR(40) NOT NULL, + id VARCHAR(64) NOT NULL, + action_profile JSONB NOT NULL, + UNIQUE (tenant, id) +); +CREATE UNIQUE INDEX action_profiles_idx ON action_profiles ("id"); diff --git a/engine/models.go b/engine/models.go index 2aee1de6a..341fff727 100644 --- a/engine/models.go +++ b/engine/models.go @@ -415,3 +415,14 @@ type IPAllocationMdl struct { func (IPAllocationMdl) TableName() string { return utils.TBLIPAllocations } + +type ActionProfileJSONMdl struct { + PK uint `gorm:"primary_key"` + Tenant string `index:"0" re:".*"` + ID string `index:"1" re:".*"` + ActionProfile utils.JSONB `gorm:"type:jsonb" index:"2" re:".*"` +} + +func (ActionProfileJSONMdl) TableName() string { + return utils.TBLActionProfilesJSON +} diff --git a/engine/storage_sql.go b/engine/storage_sql.go index eb11530ee..1814be9ce 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -104,6 +104,8 @@ func (sqls *SQLStorage) GetKeysForPrefix(ctx *context.Context, prefix string) (k keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLIPProfiles, tntID) case utils.IPAllocationsPrefix: keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLIPAllocations, tntID) + case utils.ActionProfilePrefix: + keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLActionProfilesJSON, tntID) default: err = fmt.Errorf("unsupported prefix in GetKeysForPrefix: %q", prefix) } @@ -488,7 +490,7 @@ func (sqls *SQLStorage) GetIPAllocationsDrv(ctx *context.Context, tenant, id str if len(result) == 0 { return nil, utils.ErrNotFound } - return utils.MapStringInterfaceToIPAllocations(result[0].IPAllocation) + return utils.MapStringInterfaceToIPAllocations(result[0].IPAllocation), nil } func (sqls *SQLStorage) SetIPAllocationsDrv(ctx *context.Context, ip *utils.IPAllocations) error { @@ -523,6 +525,51 @@ func (sqls *SQLStorage) RemoveIPAllocationsDrv(ctx *context.Context, tenant, id return nil } +func (sqls *SQLStorage) GetActionProfileDrv(ctx *context.Context, tenant, id string) (ap *utils.ActionProfile, err error) { + var result []*ActionProfileJSONMdl + if err := sqls.db.Model(&ActionProfileJSONMdl{}).Where(&ActionProfileJSONMdl{Tenant: tenant, + ID: id}).Find(&result).Error; err != nil { + return nil, err + } + if len(result) == 0 { + return nil, utils.ErrNotFound + } + ap, err = utils.MapStringInterfaceToActionProfile(result[0].ActionProfile) + return +} + +func (sqls *SQLStorage) SetActionProfileDrv(ctx *context.Context, ap *utils.ActionProfile) (err error) { + tx := sqls.db.Begin() + mdl := &ActionProfileJSONMdl{ + Tenant: ap.Tenant, + ID: ap.ID, + ActionProfile: ap.AsMapStringInterface(), + } + if err := tx.Model(&ActionProfileJSONMdl{}).Where( + ActionProfileJSONMdl{Tenant: mdl.Tenant, ID: mdl.ID}).Delete( + ActionProfileJSONMdl{}).Error; err != nil { + tx.Rollback() + return err + } + if err := tx.Save(mdl).Error; err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (sqls *SQLStorage) RemoveActionProfileDrv(ctx *context.Context, tenant, id string) (err error) { + tx := sqls.db.Begin() + if err := tx.Model(&ActionProfileJSONMdl{}).Where(&ActionProfileJSONMdl{Tenant: tenant, ID: id}). + Delete(&ActionProfileJSONMdl{}).Error; err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + // AddLoadHistory DataDB method not implemented yet func (sqls *SQLStorage) AddLoadHistory(ldInst *utils.LoadInstance, loadHistSize int, transactionID string) error { @@ -815,20 +862,6 @@ func (sqls *SQLStorage) RemoveRateProfileDrv(ctx *context.Context, tenant, id st return utils.ErrNotImplemented } -// DataDB method not implemented yet -func (sqls *SQLStorage) GetActionProfileDrv(ctx *context.Context, tenant, id string) (ap *utils.ActionProfile, err error) { - return nil, utils.ErrNotImplemented -} - -// DataDB method not implemented yet -func (sqls *SQLStorage) SetActionProfileDrv(ctx *context.Context, ap *utils.ActionProfile) (err error) { - return utils.ErrNotImplemented -} - -func (sqls *SQLStorage) RemoveActionProfileDrv(ctx *context.Context, tenant, id string) (err error) { - return utils.ErrNotImplemented -} - // GetIndexesDrv DataDB method not implemented yet func (sqls *SQLStorage) GetIndexesDrv(ctx *context.Context, idxItmType, tntCtx, idxKey, transactionID string) (indexes map[string]utils.StringSet, err error) { return nil, utils.ErrNotImplemented diff --git a/general_tests/dynamic_thresholds_it_test.go b/general_tests/dynamic_thresholds_it_test.go index 4200212ab..e798855b9 100644 --- a/general_tests/dynamic_thresholds_it_test.go +++ b/general_tests/dynamic_thresholds_it_test.go @@ -40,13 +40,47 @@ func TestDynThdIT(t *testing.T) { case utils.MetaInternal: dbCfg = engine.InternalDBCfg case utils.MetaRedis: - t.SkipNow() - case utils.MetaMySQL: dbCfg = engine.MySQLDBCfg + case utils.MetaMySQL: + dbCfg = engine.DBCfg{DB: &engine.DBParams{ + DBConns: map[string]engine.DBConn{ + utils.StorDB: { + Type: utils.StringPointer(utils.MetaMySQL), + Host: utils.StringPointer("127.0.0.1"), + Port: utils.IntPointer(3306), + Name: utils.StringPointer(utils.CGRateSLwr), + User: utils.StringPointer(utils.CGRateSLwr), + Password: utils.StringPointer("CGRateS.org"), + }, + }, + Items: map[string]engine.Item{ + utils.MetaActionProfiles: { + Limit: utils.IntPointer(-1), + DbConn: utils.StringPointer(utils.StorDB), + }, + }, + }} case utils.MetaMongo: dbCfg = engine.MongoDBCfg case utils.MetaPostgres: - dbCfg = engine.PostgresDBCfg + dbCfg = engine.DBCfg{DB: &engine.DBParams{ + DBConns: map[string]engine.DBConn{ + utils.StorDB: { + Type: utils.StringPointer(utils.MetaPostgres), + Host: utils.StringPointer("127.0.0.1"), + Port: utils.IntPointer(5432), + Name: utils.StringPointer(utils.CGRateSLwr), + User: utils.StringPointer(utils.CGRateSLwr), + Password: utils.StringPointer("CGRateS.org"), + }, + }, + Items: map[string]engine.Item{ + utils.MetaActionProfiles: { + Limit: utils.IntPointer(-1), + DbConn: utils.StringPointer(utils.StorDB), + }, + }, + }} default: t.Fatal("Unknown Database type") } diff --git a/utils/actions.go b/utils/actions.go index 9adf11d60..4c682e650 100644 --- a/utils/actions.go +++ b/utils/actions.go @@ -291,6 +291,120 @@ func (ap *ActionProfile) FieldAsInterface(fldPath []string) (_ any, err error) { } } +// AsMapStringInterface converts ActionProfile struct to map[string]any +func (ap *ActionProfile) AsMapStringInterface() map[string]any { + if ap == nil { + return nil + } + return map[string]any{ + Tenant: ap.Tenant, + ID: ap.ID, + FilterIDs: ap.FilterIDs, + Weights: ap.Weights, + Blockers: ap.Blockers, + Schedule: ap.Schedule, + Targets: ap.Targets, + Actions: ap.Actions, + } +} + +// MapStringInterfaceToActionProfile converts map[string]any to ActionProfile struct +func MapStringInterfaceToActionProfile(m map[string]any) (ap *ActionProfile, err error) { + ap = &ActionProfile{} + if v, ok := m[Tenant].(string); ok { + ap.Tenant = v + } + if v, ok := m[ID].(string); ok { + ap.ID = v + } + ap.FilterIDs = InterfaceToStringSlice(m[FilterIDs]) + ap.Weights = InterfaceToDynamicWeights(m[Weights]) + ap.Blockers = InterfaceToDynamicBlockers(m[Blockers]) + if v, ok := m[Schedule].(string); ok { + ap.Schedule = v + } + ap.Targets = InterfaceToMapStringStringSet(m[Targets]) + if ap.Actions, err = InterfaceToAPActions(m[Actions]); err != nil { + return nil, err + } + return ap, nil +} + +// InterfaceToAPActions converts any to []*APAction +func InterfaceToAPActions(m any) ([]*APAction, error) { + v, ok := m.([]any) + if !ok { + return nil, nil + } + + actions := make([]*APAction, 0, len(v)) + for _, actionAny := range v { + if actionMap, ok := actionAny.(map[string]any); ok { + action, err := MapToAPAction(actionMap) + if err != nil { + return nil, err + } + actions = append(actions, action) + } + } + return actions, nil +} + +// MapToAPAction converts map[string]any to APAction struct +func MapToAPAction(m map[string]any) (*APAction, error) { + action := &APAction{} + if v, ok := m[ID].(string); ok { + action.ID = v + } + action.FilterIDs = InterfaceToStringSlice(m[FilterIDs]) + if v, ok := m[TTL].(string); ok { + if dur, err := time.ParseDuration(v); err != nil { + return nil, err + } else { + action.TTL = dur + } + } else if v, ok := m[TTL].(float64); ok { // for -1 cases + action.TTL = time.Duration(v) + } + if v, ok := m[Type].(string); ok { + action.Type = v + } + if v, ok := m[Opts].(map[string]any); ok { + action.Opts = v + } + action.Weights = InterfaceToDynamicWeights(m[Weights]) + action.Blockers = InterfaceToDynamicBlockers(m[Blockers]) + if v, ok := m[Diktats].([]any); ok { + action.Diktats = make([]*APDiktat, 0, len(v)) + for _, diktatAny := range v { + if diktatMap, ok := diktatAny.(map[string]any); ok { + diktat, err := MapToAPDiktat(diktatMap) + if err != nil { + return nil, err + } + action.Diktats = append(action.Diktats, diktat) + } + } + } + return action, nil +} + +// MapToAPDiktat converts map[string]any to APDiktat struct +func MapToAPDiktat(m map[string]any) (*APDiktat, error) { + diktat := &APDiktat{} + if v, ok := m[ID].(string); ok { + diktat.ID = v + } + diktat.FilterIDs = InterfaceToStringSlice(m[FilterIDs]) + + if v, ok := m[Opts].(map[string]any); ok { + diktat.Opts = v + } + diktat.Weights = InterfaceToDynamicWeights(m[Weights]) + diktat.Blockers = InterfaceToDynamicBlockers(m[Blockers]) + return diktat, nil +} + // APAction defines action related information used within an ActionProfile. type APAction struct { ID string // Action ID diff --git a/utils/consts.go b/utils/consts.go index b3397fd30..8446f81b9 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -1897,27 +1897,28 @@ const ( // Table Name const ( - TBLTPResources = "tp_resources" - TBLTPStats = "tp_stats" - TBLTPRankings = "tp_rankings" - TBLTPTrends = "tp_trends" - TBLTPThresholds = "tp_thresholds" - TBLTPFilters = "tp_filters" - SessionCostsTBL = "session_costs" - CDRsTBL = "cdrs" - TBLTPRoutes = "tp_routes" - TBLTPAttributes = "tp_attributes" - TBLTPChargers = "tp_chargers" - TBLVersions = "versions" - TBLAccounts = "accounts" - TBLIPProfiles = "ip_profiles" - TBLIPAllocations = "ip_allocations" - OldSMCosts = "sm_costs" - TBLTPDispatchers = "tp_dispatcher_profiles" - TBLTPDispatcherHosts = "tp_dispatcher_hosts" - TBLTPRateProfiles = "tp_rate_profiles" - TBLTPActionProfiles = "tp_action_profiles" - TBLTPAccounts = "tp_accounts" + TBLTPResources = "tp_resources" + TBLTPStats = "tp_stats" + TBLTPRankings = "tp_rankings" + TBLTPTrends = "tp_trends" + TBLTPThresholds = "tp_thresholds" + TBLTPFilters = "tp_filters" + SessionCostsTBL = "session_costs" + CDRsTBL = "cdrs" + TBLTPRoutes = "tp_routes" + TBLTPAttributes = "tp_attributes" + TBLTPChargers = "tp_chargers" + TBLVersions = "versions" + TBLAccounts = "accounts" + TBLIPProfiles = "ip_profiles" + TBLIPAllocations = "ip_allocations" + TBLActionProfilesJSON = "action_profiles" + OldSMCosts = "sm_costs" + TBLTPDispatchers = "tp_dispatcher_profiles" + TBLTPDispatcherHosts = "tp_dispatcher_hosts" + TBLTPRateProfiles = "tp_rate_profiles" + TBLTPActionProfiles = "tp_action_profiles" + TBLTPAccounts = "tp_accounts" ) // Cache Name diff --git a/utils/ips.go b/utils/ips.go index 26921268f..5f0458e57 100644 --- a/utils/ips.go +++ b/utils/ips.go @@ -658,7 +658,9 @@ func MapStringInterfaceToIPProfile(m map[string]any) (*IPProfile, error) { ipp.FilterIDs = InterfaceToStringSlice(m[FilterIDs]) ipp.Weights = InterfaceToDynamicWeights(m[Weights]) if v, ok := m[TTL].(string); ok { - if dur, err := time.ParseDuration(v); err == nil { + if dur, err := time.ParseDuration(v); err != nil { + return nil, err + } else { ipp.TTL = dur } } else if v, ok := m[TTL].(float64); ok { // for -1 cases @@ -752,7 +754,7 @@ func IPAllocationsLockKey(tnt, id string) string { return ConcatenatedKey(CacheIPAllocations, tnt, id) } -// AsMapStringInterface converts IPProfile struct to map[string]any +// AsMapStringInterface converts IPAllocations struct to map[string]any func (p *IPAllocations) AsMapStringInterface() map[string]any { if p == nil { return nil @@ -766,7 +768,7 @@ func (p *IPAllocations) AsMapStringInterface() map[string]any { } // MapStringInterfaceToIPAllocations converts map[string]any to IPAllocations struct -func MapStringInterfaceToIPAllocations(m map[string]any) (*IPAllocations, error) { +func MapStringInterfaceToIPAllocations(m map[string]any) *IPAllocations { ipa := &IPAllocations{} if v, ok := m[Tenant].(string); ok { @@ -777,7 +779,7 @@ func MapStringInterfaceToIPAllocations(m map[string]any) (*IPAllocations, error) } ipa.Allocations = InterfaceToAllocations(m[Allocations]) ipa.TTLIndex = InterfaceToStringSlice(m[TTLIndex]) - return ipa, nil + return ipa } // InterfaceToAllocations converts any to map[string]*PoolAllocation diff --git a/utils/stringset.go b/utils/stringset.go index 2cbee4d79..158f974f7 100644 --- a/utils/stringset.go +++ b/utils/stringset.go @@ -161,3 +161,22 @@ func (s StringSet) FieldAsString(fldPath []string) (_ string, err error) { } return "{}", nil // noting in it as is a empty structure } + +// InterfaceToMapStringStringSet converts map[string]any to map[string]StringSet +func InterfaceToMapStringStringSet(m any) map[string]StringSet { + v, ok := m.(map[string]any) + if !ok { + return nil + } + targets := make(map[string]StringSet) + for key, val := range v { + if valMap, ok := val.(map[string]any); ok { + stringSet := make(StringSet) + for k := range valMap { + stringSet[k] = struct{}{} + } + targets[key] = stringSet + } + } + return targets +}