ips: add ClearAllocations API

This commit is contained in:
ionutboangiu
2025-08-07 12:26:38 +03:00
committed by Dan Christian Bogos
parent 82cfff3cff
commit 707826359b
6 changed files with 169 additions and 35 deletions

View File

@@ -211,3 +211,9 @@ func (ipS *IPSv1) V1ReleaseIP(ctx *context.Context, args *utils.CGREvent, reply
func (ipS *IPSv1) V1GetIPAllocations(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *utils.IPAllocations) error {
return ipS.ips.V1GetIPAllocations(ctx, arg, reply)
}
// V1ClearIPAllocations clears IP allocations from an IPAllocations object.
// If args.AllocationIDs is empty or nil, all allocations will be cleared.
func (ipS *IPSv1) V1ClearIPAllocations(ctx *context.Context, arg *utils.ClearIPAllocationsArgs, reply *string) error {
return ipS.ips.V1ClearIPAllocations(ctx, arg, reply)
}

View File

@@ -154,15 +154,21 @@ Checks if an IP can be allocated without actually allocating it (dry run).
::
{
"Tenant": "cgrates.org",
"ID": "unique_event_id",
"Event": {
"Account": "1001",
"Destination": "1002"
},
"APIOpts": {
"*ipAllocationID": "ip_allocation_abc123"
}
"method": "IPsV1.AuthorizeIP",
"params": [
{
"Tenant": "cgrates.org",
"ID": "unique_event_id",
"Event": {
"Account": "1001",
"Destination": "1002"
},
"APIOpts": {
"*ipAllocationID": "ip_allocation_abc123"
}
}
],
"id": 1
}
**Returns:**
@@ -236,6 +242,40 @@ Gets the matching IPAllocations object for a specific event.
- IPAllocations object for the matching profile
V1ClearIPAllocations
~~~~~~~~~~~~~~~~~~~~
Clears IP allocations from an IPAllocations object.
**Request:**
::
{
"method": "IPsV1.ClearIPAllocations",
"params": [
{
"Tenant": "cgrates.org",
"ID": "profile_id",
"AllocationIDs": [
"alloc1",
"alloc2"
]
}
],
"id": 6
}
**Parameters:**
- Tenant and Profile ID (required)
- AllocationIDs: Array of specific allocation IDs to clear (optional - if empty or omitted, all allocations will be cleared)
**Returns:**
- Success confirmation
- Error if any specified allocation IDs don't exist
Use Cases
---------

View File

@@ -275,7 +275,7 @@ func (s *IPService) V1ReleaseIP(ctx *context.Context, args *utils.CGREvent, repl
return nil
}
// V1GetIPAllocations returns a resource configuration
// V1GetIPAllocations returns all IP allocations for a tenantID.
func (s *IPService) V1GetIPAllocations(ctx *context.Context, arg *utils.TenantIDWithAPIOpts, reply *utils.IPAllocations) error {
if missing := utils.MissingStructFields(arg, []string{utils.ID}); len(missing) != 0 { //Params missing
return utils.NewErrMandatoryIeMissing(missing...)
@@ -298,3 +298,35 @@ func (s *IPService) V1GetIPAllocations(ctx *context.Context, arg *utils.TenantID
*reply = *ip
return nil
}
// V1ClearIPAllocations clears IP allocations from an IPAllocations object.
// If args.AllocationIDs is empty or nil, all allocations will be cleared.
func (s *IPService) V1ClearIPAllocations(ctx *context.Context, args *utils.ClearIPAllocationsArgs, reply *string) error {
if missing := utils.MissingStructFields(args, []string{utils.ID}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
tnt := args.Tenant
if tnt == utils.EmptyString {
tnt = s.cfg.GeneralCfg().DefaultTenant
}
lkID := guardian.Guardian.GuardIDs(utils.EmptyString,
config.CgrConfig().GeneralCfg().LockingTimeout,
utils.IPAllocationsLockKey(tnt, args.ID))
defer guardian.Guardian.UnguardIDs(lkID)
allocs, err := s.dm.GetIPAllocations(ctx, tnt, args.ID, true, true, utils.NonTransactional)
if err != nil {
return err
}
if err := allocs.ClearAllocations(args.AllocationIDs); err != nil {
return err
}
if err := s.storeIPAllocations(ctx, allocs); err != nil {
return err
}
*reply = utils.OK
return nil
}

View File

@@ -95,26 +95,12 @@ func TestIPsIT(t *testing.T) {
"exists_indexed_fields": [],
"notexists_indexed_fields": [],
"opts":{
"*allocationID": [
{
"Tenant": "cgrates.org",
"FilterIDs": ["*string:~*req.Account:1001"],
"Value": "cfg_allocation"
}
],
// "*ttl": [
// {
// "Tenant": "*any",
// "FilterIDs": [],
// "Value": "72h"
// }
// ],
// "*units": [
// {
// "Tenant": "*any",
// "FilterIDs": [],
// "Value": 1
// }
// "*allocationID": [
// {
// "Tenant": "cgrates.org",
// "FilterIDs": ["*string:~*req.Account:1001"],
// "Value": "cfg_allocation"
// }
// ]
}
},
@@ -236,6 +222,7 @@ cgrates.org,IPs2,*string:~*req.Account:1002,;20,2s,false,POOL1,*string:~*req.Des
ID: "GetIPsForEvent1",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "2001",
},
APIOpts: map[string]any{
utils.OptsIPsAllocationID: allocID,
@@ -251,8 +238,11 @@ cgrates.org,IPs2,*string:~*req.Account:1002,;20,2s,false,POOL1,*string:~*req.Des
ID: "AuthorizeIP1",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "2001",
},
APIOpts: map[string]any{
utils.OptsIPsAllocationID: allocID,
},
APIOpts: map[string]any{},
}, &allocIP); err != nil {
t.Error(err)
}
@@ -263,8 +253,11 @@ cgrates.org,IPs2,*string:~*req.Account:1002,;20,2s,false,POOL1,*string:~*req.Des
ID: "AllocateIP1",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "2001",
},
APIOpts: map[string]any{
utils.OptsIPsAllocationID: allocID,
},
APIOpts: map[string]any{},
}, &allocIP); err != nil {
t.Error(err)
}
@@ -276,8 +269,20 @@ cgrates.org,IPs2,*string:~*req.Account:1002,;20,2s,false,POOL1,*string:~*req.Des
ID: "ReleaseIP1",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "2001",
},
APIOpts: map[string]any{},
APIOpts: map[string]any{
utils.OptsIPsAllocationID: allocID,
},
}, &reply); err != nil {
t.Error(err)
}
if err := client.Call(context.Background(), utils.IPsV1ClearIPAllocations,
&utils.ClearIPAllocationsArgs{
Tenant: "cgrates.org",
ID: "IPs1",
// AllocationIDs: []string{allocID},
}, &reply); err != nil {
t.Error(err)
}
@@ -298,7 +303,7 @@ cgrates.org,IPs2,*string:~*req.Account:1002,;20,2s,false,POOL1,*string:~*req.Des
},
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "1002",
utils.Destination: "2001",
utils.SetupTime: "2018-01-07T17:00:00Z",
},
}, &reply); err != nil {
@@ -313,7 +318,7 @@ cgrates.org,IPs2,*string:~*req.Account:1002,;20,2s,false,POOL1,*string:~*req.Des
},
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "1002",
utils.Destination: "2001",
utils.SetupTime: "2018-01-07T17:00:00Z",
},
}, &reply); err != nil {
@@ -321,6 +326,7 @@ cgrates.org,IPs2,*string:~*req.Account:1002,;20,2s,false,POOL1,*string:~*req.Des
}
})
}
func BenchmarkIPsAuthorize(b *testing.B) {
cfg := config.NewDefaultCGRConfig()
dataDB, _ := engine.NewInternalDB(nil, nil, nil, cfg.DataDbCfg().Items)

View File

@@ -1679,6 +1679,7 @@ const (
IPsV1AuthorizeIP = "IPsV1.AuthorizeIP"
IPsV1AllocateIP = "IPsV1.AllocateIP"
IPsV1ReleaseIP = "IPsV1.ReleaseIP"
IPsV1ClearIPAllocations = "IPsV1.ClearIPAllocations"
AdminSv1SetIPProfile = "AdminSv1.SetIPProfile"
AdminSv1GetIPProfiles = "AdminSv1.GetIPProfiles"
AdminSv1RemoveIPProfile = "AdminSv1.RemoveIPProfile"

View File

@@ -416,6 +416,15 @@ type IPAllocationsWithAPIOpts struct {
APIOpts map[string]any
}
// ClearIPAllocationsArgs contains arguments for clearing IP allocations.
// If AllocationIDs is empty or nil, all allocations will be cleared.
type ClearIPAllocationsArgs struct {
Tenant string
ID string
AllocationIDs []string
APIOpts map[string]any
}
// ComputeUnexported populates lookup maps and profile reference from exported fields.
// Must be called after retrieving from DB.
func (a *IPAllocations) ComputeUnexported(prfl *IPProfile) error {
@@ -459,6 +468,46 @@ func (a *IPAllocations) ReleaseAllocation(allocID string) error {
return nil
}
// ClearAllocations clears specified IP allocations or all allocations if allocIDs is empty/nil.
// Either all specified IDs exist and get cleared, or none are cleared and an error is returned.
func (a *IPAllocations) ClearAllocations(allocIDs []string) error {
if len(allocIDs) == 0 {
clear(a.Allocations)
clear(a.poolAllocs)
a.TTLIndex = a.TTLIndex[:0] // maintain capacity
return nil
}
// Validate all IDs exist before clearing any.
var notFound []string
for _, allocID := range allocIDs {
if _, has := a.Allocations[allocID]; !has {
notFound = append(notFound, allocID)
}
}
if len(notFound) > 0 {
return fmt.Errorf("cannot find allocation records with ids: %v", notFound)
}
for _, allocID := range allocIDs {
alloc := a.Allocations[allocID]
if poolMap, hasPool := a.poolAllocs[alloc.PoolID]; hasPool {
delete(poolMap, alloc.Address)
}
if a.prfl.TTL > 0 {
for i, refID := range a.TTLIndex {
if refID == allocID {
a.TTLIndex = slices.Delete(a.TTLIndex, i, i+1)
break
}
}
}
delete(a.Allocations, allocID)
}
return nil
}
// AllocateIPOnPool allocates an IP from the specified pool or refreshes
// existing allocation. If dryRun is true, checks availability without
// allocating.