Make Charger Profiles storable in MySQL and Postgres

This commit is contained in:
arberkatellari
2025-11-04 16:39:56 +02:00
committed by Dan Christian Bogos
parent d78f34bdc5
commit eda80242eb
14 changed files with 257 additions and 48 deletions

View File

@@ -86,11 +86,11 @@ func TestChargersIT(t *testing.T) {
case utils.MetaMongo:
chargersConfigDIR = "apis_chargers_mongo"
case utils.MetaRedis:
t.SkipNow()
chargersConfigDIR = "apis_chargers_redis"
case utils.MetaMySQL:
chargersConfigDIR = "apis_chargers_mysql"
case utils.MetaPostgres:
t.SkipNow()
chargersConfigDIR = "apis_chargers_postgres"
default:
t.Fatal("Unknown Database type")
}

View File

@@ -130,6 +130,8 @@ const CGRATES_CFG_JSON = `
"*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"},
"*versions": {"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"},
// compatible db types: <*internal|*redis|*mongo>
"*actions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
@@ -142,10 +144,8 @@ const CGRATES_CFG_JSON = `
"*filters": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
"*route_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
"*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"},
"*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"},
"*ip_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "*default"},
"*stat_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "*default"},

View File

@@ -1061,7 +1061,8 @@ func (cfg *CGRConfig) checkConfigSanity() error {
utils.Internal, utils.Redis, utils.Mongo}
if item != utils.MetaAccounts && item != utils.MetaIPProfiles &&
item != utils.MetaIPAllocations && item != utils.MetaActionProfiles {
item != utils.MetaIPAllocations && item != utils.MetaActionProfiles &&
item != utils.MetaChargerProfiles {
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,

View File

@@ -33,7 +33,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"},
"*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}
}
},

View File

@@ -0,0 +1,54 @@
{
"general": {
"reply_timeout": "50s"
},
"logger": {
"level": 7
},
"listen": {
"rpc_json": ":2012",
"rpc_gob": ":2013",
"http": ":2080"
},
"db": {
"db_conns": {
"*default": {
"db_type": "redis",
"db_host": "127.0.0.1",
"db_port": 6379,
"db_name": "10",
"db_user": "cgrates"
},
"StorDB": { // The id of the DB connection
"db_type": "postgres", // db type: <internal|redis|mysql|mongo|postgres>
"db_host": "127.0.0.1",
"db_port": 5432, // db port to reach the database
"db_name": "cgrates", // the host to connect to
"db_user": "cgrates",
"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"},
"*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}
}
},
"attributes": {
"enabled": true
},
"chargers": {
"enabled": true,
"attributes_conns": ["*internal"]
},
"admins": {
"enabled": true
}
}

View File

@@ -0,0 +1,53 @@
{
"general": {
"reply_timeout": "50s"
},
"logger": {
"level": 7
},
"listen": {
"rpc_json": ":2012",
"rpc_gob": ":2013",
"http": ":2080"
},
"db": {
"db_conns": {
"*default": {
"db_type": "redis",
"db_host": "127.0.0.1",
"db_port": 6379,
"db_name": "10",
"db_user": "cgrates"
},
"StorDB": {
"db_type": "mysql",
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "cgrates",
"db_user": "cgrates",
"db_password": "CGRateS.org"
}
},
"items": {
"*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}
}
},
"attributes": {
"enabled": true
},
"chargers": {
"enabled": true,
"attributes_conns": ["*internal"]
},
"admins": {
"enabled": true
}
}

View File

@@ -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"}
}
},

View File

@@ -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"}
}
},

View File

@@ -44,4 +44,15 @@ CREATE TABLE action_profiles (
PRIMARY KEY (`pk`),
UNIQUE KEY unique_tenant_id (`tenant`, `id`)
);
CREATE UNIQUE INDEX action_profiles_idx ON action_profiles (`id`);
CREATE UNIQUE INDEX action_profiles_idx ON action_profiles (`id`);
DROP TABLE IF EXISTS charger_profiles;
CREATE TABLE charger_profiles (
`pk` int(11) NOT NULL AUTO_INCREMENT,
`tenant` VARCHAR(40) NOT NULL,
`id` VARCHAR(64) NOT NULL,
`charger_profile` JSON NOT NULL,
PRIMARY KEY (`pk`),
UNIQUE KEY unique_tenant_id (`tenant`, `id`)
);
CREATE UNIQUE INDEX charger_profiles_idx ON charger_profiles (`id`);

View File

@@ -43,3 +43,14 @@ CREATE TABLE action_profiles (
UNIQUE (tenant, id)
);
CREATE UNIQUE INDEX action_profiles_idx ON action_profiles ("id");
DROP TABLE IF EXISTS charger_profiles;
CREATE TABLE charger_profiles (
pk SERIAL PRIMARY KEY,
tenant VARCHAR(40) NOT NULL,
id VARCHAR(64) NOT NULL,
charger_profile JSONB NOT NULL,
UNIQUE (tenant, id)
);
CREATE UNIQUE INDEX charger_profiles_idx ON charger_profiles ("id");

View File

@@ -426,3 +426,14 @@ type ActionProfileJSONMdl struct {
func (ActionProfileJSONMdl) TableName() string {
return utils.TBLActionProfilesJSON
}
type ChargerProfileMdl struct {
PK uint `gorm:"primary_key"`
Tenant string `index:"0" re:".*"`
ID string `index:"1" re:".*"`
ChargerProfile utils.JSONB `gorm:"type:jsonb" index:"2" re:".*"`
}
func (ChargerProfileMdl) TableName() string {
return utils.TBLChargerProfilesJSON
}

View File

@@ -106,6 +106,8 @@ func (sqls *SQLStorage) GetKeysForPrefix(ctx *context.Context, prefix string) (k
keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLIPAllocations, tntID)
case utils.ActionProfilePrefix:
keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLActionProfilesJSON, tntID)
case utils.ChargerProfilePrefix:
keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLChargerProfilesJSON, tntID)
default:
err = fmt.Errorf("unsupported prefix in GetKeysForPrefix: %q", prefix)
}
@@ -534,8 +536,7 @@ func (sqls *SQLStorage) GetActionProfileDrv(ctx *context.Context, tenant, id str
if len(result) == 0 {
return nil, utils.ErrNotFound
}
ap, err = utils.MapStringInterfaceToActionProfile(result[0].ActionProfile)
return
return utils.MapStringInterfaceToActionProfile(result[0].ActionProfile)
}
func (sqls *SQLStorage) SetActionProfileDrv(ctx *context.Context, ap *utils.ActionProfile) (err error) {
@@ -570,6 +571,51 @@ func (sqls *SQLStorage) RemoveActionProfileDrv(ctx *context.Context, tenant, id
return nil
}
func (sqls *SQLStorage) GetChargerProfileDrv(_ *context.Context, tenant, id string) (cp *utils.ChargerProfile, err error) {
var result []*ChargerProfileMdl
if err := sqls.db.Model(&ChargerProfileMdl{}).Where(&ChargerProfileMdl{Tenant: tenant,
ID: id}).Find(&result).Error; err != nil {
return nil, err
}
if len(result) == 0 {
return nil, utils.ErrNotFound
}
return utils.MapStringInterfaceToChargerProfile(result[0].ChargerProfile)
}
func (sqls *SQLStorage) SetChargerProfileDrv(_ *context.Context, cp *utils.ChargerProfile) (err error) {
tx := sqls.db.Begin()
mdl := &ChargerProfileMdl{
Tenant: cp.Tenant,
ID: cp.ID,
ChargerProfile: cp.AsMapStringInterface(),
}
if err := tx.Model(&ChargerProfileMdl{}).Where(
ChargerProfileMdl{Tenant: mdl.Tenant, ID: mdl.ID}).Delete(
ChargerProfileMdl{}).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) RemoveChargerProfileDrv(_ *context.Context, tenant, id string) (err error) {
tx := sqls.db.Begin()
if err := tx.Model(&ChargerProfileMdl{}).Where(&ChargerProfileMdl{Tenant: tenant, ID: id}).
Delete(&ChargerProfileMdl{}).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 {
@@ -807,21 +853,6 @@ func (sqls *SQLStorage) RemoveAttributeProfileDrv(ctx *context.Context, tenant,
return utils.ErrNotImplemented
}
// DataDB method not implemented yet
func (sqls *SQLStorage) GetChargerProfileDrv(_ *context.Context, tenant, id string) (r *utils.ChargerProfile, err error) {
return nil, utils.ErrNotImplemented
}
// DataDB method not implemented yet
func (sqls *SQLStorage) SetChargerProfileDrv(_ *context.Context, r *utils.ChargerProfile) (err error) {
return utils.ErrNotImplemented
}
// DataDB method not implemented yet
func (sqls *SQLStorage) RemoveChargerProfileDrv(_ *context.Context, tenant, id string) (err error) {
return utils.ErrNotImplemented
}
// GetStorageType returns the storage type that is being used
func (sqls *SQLStorage) GetStorageType() string {
return utils.MetaMySQL

View File

@@ -173,3 +173,38 @@ type ChargerProfileWithAPIOpts struct {
*ChargerProfile
APIOpts map[string]any
}
// AsMapStringInterface converts ChargerProfile struct to map[string]any
func (cp *ChargerProfile) AsMapStringInterface() map[string]any {
if cp == nil {
return nil
}
return map[string]any{
Tenant: cp.Tenant,
ID: cp.ID,
FilterIDs: cp.FilterIDs,
Weights: cp.Weights,
Blockers: cp.Blockers,
RunID: cp.RunID,
AttributeIDs: cp.AttributeIDs,
}
}
// MapStringInterfaceToChargerProfile converts map[string]any to ChargerProfile struct
func MapStringInterfaceToChargerProfile(m map[string]any) (cp *ChargerProfile, err error) {
cp = &ChargerProfile{}
if v, ok := m[Tenant].(string); ok {
cp.Tenant = v
}
if v, ok := m[ID].(string); ok {
cp.ID = v
}
cp.FilterIDs = InterfaceToStringSlice(m[FilterIDs])
cp.Weights = InterfaceToDynamicWeights(m[Weights])
cp.Blockers = InterfaceToDynamicBlockers(m[Blockers])
if v, ok := m[RunID].(string); ok {
cp.RunID = v
}
cp.AttributeIDs = InterfaceToStringSlice(m[AttributeIDs])
return cp, nil
}

View File

@@ -1897,28 +1897,29 @@ 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"
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"
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"
TBLChargerProfilesJSON = "charger_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