mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
817 lines
20 KiB
Go
817 lines
20 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"net/netip"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/cgrates/guardian"
|
|
)
|
|
|
|
// IPProfile defines the configuration of an IPAllocations object.
|
|
type IPProfile struct {
|
|
Tenant string
|
|
ID string
|
|
FilterIDs []string
|
|
Weights DynamicWeights
|
|
TTL time.Duration
|
|
Stored bool
|
|
Pools []*IPPool
|
|
|
|
lockID string // reference ID of lock used when matching the IPProfile
|
|
}
|
|
|
|
// IPProfileWithAPIOpts wraps IPProfile with APIOpts.
|
|
type IPProfileWithAPIOpts struct {
|
|
*IPProfile
|
|
APIOpts map[string]any
|
|
}
|
|
|
|
// TenantID returns the concatenated tenant and ID.
|
|
func (p *IPProfile) TenantID() string {
|
|
return ConcatenatedKey(p.Tenant, p.ID)
|
|
}
|
|
|
|
// Clone creates a deep copy of IPProfile for thread-safe use.
|
|
func (p *IPProfile) Clone() *IPProfile {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
pools := make([]*IPPool, 0, len(p.Pools))
|
|
for _, pool := range p.Pools {
|
|
pools = append(pools, pool.Clone())
|
|
}
|
|
return &IPProfile{
|
|
Tenant: p.Tenant,
|
|
ID: p.ID,
|
|
FilterIDs: slices.Clone(p.FilterIDs),
|
|
Weights: p.Weights.Clone(),
|
|
TTL: p.TTL,
|
|
Stored: p.Stored,
|
|
Pools: pools,
|
|
lockID: p.lockID,
|
|
}
|
|
}
|
|
|
|
// CacheClone returns a clone of IPProfile used by ltcache CacheCloner
|
|
func (p *IPProfile) CacheClone() any {
|
|
return p.Clone()
|
|
}
|
|
|
|
func (p *IPProfile) Set(path []string, val any, newBranch bool) error {
|
|
if len(path) != 1 && len(path) != 2 {
|
|
return ErrWrongPath
|
|
}
|
|
var err error
|
|
switch path[0] {
|
|
default:
|
|
return ErrWrongPath
|
|
case Tenant:
|
|
p.Tenant = IfaceAsString(val)
|
|
case ID:
|
|
p.ID = IfaceAsString(val)
|
|
case FilterIDs:
|
|
var valA []string
|
|
valA, err = IfaceAsStringSlice(val)
|
|
p.FilterIDs = append(p.FilterIDs, valA...)
|
|
case Weights:
|
|
if val != "" {
|
|
p.Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep)
|
|
}
|
|
case TTL:
|
|
p.TTL, err = IfaceAsDuration(val)
|
|
case Stored:
|
|
p.Stored, err = IfaceAsBool(val)
|
|
case Pools:
|
|
if len(path) != 2 {
|
|
return ErrWrongPath
|
|
}
|
|
if val == "" {
|
|
return nil
|
|
}
|
|
if len(p.Pools) == 0 || newBranch {
|
|
p.Pools = append(p.Pools, new(IPPool))
|
|
}
|
|
pool := p.Pools[len(p.Pools)-1]
|
|
return pool.Set(path[1:], val, newBranch)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (p *IPProfile) Merge(other any) {
|
|
o := other.(*IPProfile)
|
|
if len(o.Tenant) != 0 {
|
|
p.Tenant = o.Tenant
|
|
}
|
|
if len(o.ID) != 0 {
|
|
p.ID = o.ID
|
|
}
|
|
p.FilterIDs = append(p.FilterIDs, o.FilterIDs...)
|
|
p.Weights = append(p.Weights, o.Weights...)
|
|
if o.TTL != 0 {
|
|
p.TTL = o.TTL
|
|
}
|
|
if o.Stored {
|
|
p.Stored = o.Stored
|
|
}
|
|
for _, pool := range o.Pools {
|
|
if idx := slices.IndexFunc(p.Pools, func(p *IPPool) bool {
|
|
return p.ID == pool.ID
|
|
}); idx != -1 {
|
|
p.Pools[idx].Merge(pool)
|
|
continue
|
|
}
|
|
p.Pools = append(p.Pools, pool)
|
|
}
|
|
}
|
|
|
|
func (p *IPProfile) String() string { return ToJSON(p) }
|
|
|
|
func (p *IPProfile) FieldAsString(fldPath []string) (string, error) {
|
|
val, err := p.FieldAsInterface(fldPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return IfaceAsString(val), nil
|
|
}
|
|
|
|
func (p *IPProfile) FieldAsInterface(fldPath []string) (any, error) {
|
|
if len(fldPath) == 0 {
|
|
return nil, ErrNotFound
|
|
}
|
|
switch fldPath[0] {
|
|
default:
|
|
fld, idx := GetPathIndex(fldPath[0])
|
|
if idx != nil {
|
|
switch fld {
|
|
case FilterIDs:
|
|
if *idx < len(p.FilterIDs) {
|
|
return p.FilterIDs[*idx], nil
|
|
}
|
|
case Pools:
|
|
if *idx < len(p.Pools) {
|
|
return p.Pools[*idx].FieldAsInterface(fldPath[1:])
|
|
}
|
|
}
|
|
}
|
|
return nil, ErrNotFound
|
|
case Tenant:
|
|
return p.Tenant, nil
|
|
case ID:
|
|
return p.ID, nil
|
|
case FilterIDs:
|
|
return p.FilterIDs, nil
|
|
case Weights:
|
|
return p.Weights, nil
|
|
case TTL:
|
|
return p.TTL, nil
|
|
case Stored:
|
|
return p.Stored, nil
|
|
case Pools:
|
|
return p.Pools, nil
|
|
}
|
|
}
|
|
|
|
// Lock acquires a guardian lock on the IPProfile and stores the lock ID.
|
|
// Uses given lockID or creates a new lock.
|
|
func (p *IPProfile) Lock(lockID string) {
|
|
if lockID == "" {
|
|
lockID = guardian.Guardian.GuardIDs("",
|
|
0, // TODO: find a way to pass timeout without importing config
|
|
IPProfileLockKey(p.Tenant, p.ID))
|
|
}
|
|
p.lockID = lockID
|
|
}
|
|
|
|
// Unlock releases the lock on the IPProfile and clears the stored lock ID.
|
|
func (p *IPProfile) Unlock() {
|
|
if p.lockID == "" {
|
|
return
|
|
}
|
|
|
|
// Store current lock ID before clearing to prevent race conditions.
|
|
id := p.lockID
|
|
p.lockID = ""
|
|
guardian.Guardian.UnguardIDs(id)
|
|
}
|
|
|
|
// IPProfileLockKey returns the ID used to lock an IPProfile with guardian.
|
|
func IPProfileLockKey(tnt, id string) string {
|
|
return ConcatenatedKey(CacheIPProfiles, tnt, id)
|
|
}
|
|
|
|
// IPPool defines a pool of IP addresses within an IPProfile.
|
|
type IPPool struct {
|
|
ID string
|
|
FilterIDs []string
|
|
Type string
|
|
Range string
|
|
Strategy string
|
|
Message string
|
|
Weights DynamicWeights
|
|
Blockers DynamicBlockers
|
|
}
|
|
|
|
// Clone creates a deep copy of Pool for thread-safe use.
|
|
func (p *IPPool) Clone() *IPPool {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
return &IPPool{
|
|
ID: p.ID,
|
|
FilterIDs: slices.Clone(p.FilterIDs),
|
|
Type: p.Type,
|
|
Range: p.Range,
|
|
Strategy: p.Strategy,
|
|
Message: p.Message,
|
|
Weights: p.Weights.Clone(),
|
|
Blockers: p.Blockers.Clone(),
|
|
}
|
|
}
|
|
|
|
func (p *IPPool) Set(path []string, val any, _ bool) error {
|
|
if len(path) != 1 {
|
|
return ErrWrongPath
|
|
}
|
|
var err error
|
|
switch path[0] {
|
|
default:
|
|
return ErrWrongPath
|
|
case ID:
|
|
p.ID = IfaceAsString(val)
|
|
case FilterIDs:
|
|
var valA []string
|
|
valA, err = IfaceAsStringSlice(val)
|
|
p.FilterIDs = append(p.FilterIDs, valA...)
|
|
case Type:
|
|
p.Type = IfaceAsString(val)
|
|
case Range:
|
|
p.Range = IfaceAsString(val)
|
|
case Strategy:
|
|
p.Strategy = IfaceAsString(val)
|
|
case Message:
|
|
p.Message = IfaceAsString(val)
|
|
case Weights:
|
|
if val != "" {
|
|
p.Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep)
|
|
}
|
|
case Blockers:
|
|
if val != "" {
|
|
p.Blockers, err = NewDynamicBlockersFromString(IfaceAsString(val), InfieldSep, ANDSep)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (p *IPPool) Merge(other any) {
|
|
o := other.(*IPPool)
|
|
|
|
// NOTE: Merge gets called when the IDs are equal, so this is a no-op.
|
|
// Kept for consistency with other components.
|
|
if len(o.ID) != 0 {
|
|
p.ID = o.ID
|
|
}
|
|
|
|
p.FilterIDs = append(p.FilterIDs, o.FilterIDs...)
|
|
if o.Type != "" {
|
|
p.Type = o.Type
|
|
}
|
|
if o.Range != "" {
|
|
p.Range = o.Range
|
|
}
|
|
if o.Strategy != "" {
|
|
p.Strategy = o.Strategy
|
|
}
|
|
if o.Message != "" {
|
|
p.Message = o.Message
|
|
}
|
|
p.Weights = append(p.Weights, o.Weights...)
|
|
p.Blockers = append(p.Blockers, o.Blockers...)
|
|
}
|
|
|
|
func (p *IPPool) String() string { return ToJSON(p) }
|
|
|
|
func (p *IPPool) FieldAsString(fldPath []string) (string, error) {
|
|
val, err := p.FieldAsInterface(fldPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return IfaceAsString(val), nil
|
|
}
|
|
|
|
func (p *IPPool) FieldAsInterface(fldPath []string) (any, error) {
|
|
if len(fldPath) == 0 {
|
|
return p, nil
|
|
}
|
|
if len(fldPath) > 1 {
|
|
return nil, ErrNotFound
|
|
}
|
|
switch fldPath[0] {
|
|
default:
|
|
fld, idx := GetPathIndex(fldPath[0])
|
|
if idx != nil {
|
|
switch fld {
|
|
case FilterIDs:
|
|
if *idx < len(p.FilterIDs) {
|
|
return p.FilterIDs[*idx], nil
|
|
}
|
|
}
|
|
}
|
|
return nil, ErrNotFound
|
|
case ID:
|
|
return p.ID, nil
|
|
case FilterIDs:
|
|
return p.FilterIDs, nil
|
|
case Type:
|
|
return p.Type, nil
|
|
case Range:
|
|
return p.Range, nil
|
|
case Strategy:
|
|
return p.Strategy, nil
|
|
case Message:
|
|
return p.Message, nil
|
|
case Weights:
|
|
return p.Weights, nil
|
|
case Blockers:
|
|
return p.Blockers, nil
|
|
}
|
|
}
|
|
|
|
// PoolAllocation represents one allocation in the pool.
|
|
type PoolAllocation struct {
|
|
PoolID string // pool ID within the IPProfile
|
|
Address netip.Addr // computed IP address
|
|
Time time.Time // when this allocation was created
|
|
}
|
|
|
|
// IsActive checks if the allocation is still active.
|
|
func (a *PoolAllocation) IsActive(ttl time.Duration) bool {
|
|
return time.Now().Before(a.Time.Add(ttl))
|
|
}
|
|
|
|
// Clone creates a deep copy of the PoolAllocation object.
|
|
func (a *PoolAllocation) Clone() *PoolAllocation {
|
|
if a == nil {
|
|
return nil
|
|
}
|
|
clone := *a
|
|
return &clone
|
|
}
|
|
|
|
// AllocatedIP represents one IP allocated on a pool, together with the message.
|
|
type AllocatedIP struct {
|
|
ProfileID string
|
|
PoolID string
|
|
Message string
|
|
Address netip.Addr
|
|
}
|
|
|
|
// AsNavigableMap implements engine.NavigableMapper.
|
|
func (ip *AllocatedIP) AsNavigableMap() map[string]*DataNode {
|
|
return map[string]*DataNode{
|
|
ProfileID: NewLeafNode(ip.ProfileID),
|
|
PoolID: NewLeafNode(ip.PoolID),
|
|
Message: NewLeafNode(ip.Message),
|
|
Address: NewLeafNode(ip.Address.String()),
|
|
}
|
|
}
|
|
|
|
// IPAllocations represents IP allocations with usage tracking and TTL management.
|
|
type IPAllocations struct {
|
|
Tenant string
|
|
ID string
|
|
Allocations map[string]*PoolAllocation // map[allocID]*PoolAllocation
|
|
TTLIndex []string // allocIDs ordered by allocation time for TTL expiry
|
|
|
|
prfl *IPProfile // cached profile configuration
|
|
poolRanges map[string]netip.Prefix // parsed CIDR ranges by pool ID
|
|
poolAllocs map[string]map[netip.Addr]string // IP to allocation ID mapping by pool (map[poolID]map[Addr]allocID)
|
|
lockID string // guardian lock reference
|
|
}
|
|
|
|
// IPAllocationsWithAPIOpts wraps IPAllocations with APIOpts.
|
|
type IPAllocationsWithAPIOpts struct {
|
|
*IPAllocations
|
|
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 sets up unexported fields based on the provided profile.
|
|
// Safe to call multiple times with the same profile.
|
|
func (a *IPAllocations) ComputeUnexported(prfl *IPProfile) error {
|
|
if prfl == nil {
|
|
return nil // nothing to compute without a profile
|
|
}
|
|
if a.prfl == prfl {
|
|
return nil // already computed for this profile
|
|
}
|
|
a.prfl = prfl
|
|
a.poolAllocs = make(map[string]map[netip.Addr]string)
|
|
for allocID, alloc := range a.Allocations {
|
|
if _, hasPool := a.poolAllocs[alloc.PoolID]; !hasPool {
|
|
a.poolAllocs[alloc.PoolID] = make(map[netip.Addr]string)
|
|
}
|
|
a.poolAllocs[alloc.PoolID][alloc.Address] = allocID
|
|
}
|
|
a.poolRanges = make(map[string]netip.Prefix)
|
|
for _, poolCfg := range a.prfl.Pools {
|
|
prefix, err := netip.ParsePrefix(poolCfg.Range)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.poolRanges[poolCfg.ID] = prefix
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReleaseAllocation releases the allocation for an ID.
|
|
func (a *IPAllocations) ReleaseAllocation(allocID string) error {
|
|
alloc, has := a.Allocations[allocID] // Get the allocation first
|
|
if !has {
|
|
return fmt.Errorf("cannot find allocation record with id: %s", 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
|
|
}
|
|
|
|
// 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.
|
|
func (a *IPAllocations) AllocateIPOnPool(allocID string, pool *IPPool,
|
|
dryRun bool) (*AllocatedIP, error) {
|
|
a.removeExpiredUnits()
|
|
if poolAlloc, has := a.Allocations[allocID]; has && !dryRun {
|
|
poolAlloc.Time = time.Now()
|
|
if a.prfl.TTL > 0 {
|
|
a.removeAllocFromTTLIndex(allocID)
|
|
}
|
|
a.TTLIndex = append(a.TTLIndex, allocID)
|
|
return &AllocatedIP{
|
|
ProfileID: a.ID,
|
|
PoolID: pool.ID,
|
|
Message: pool.Message,
|
|
Address: poolAlloc.Address,
|
|
}, nil
|
|
}
|
|
poolRange := a.poolRanges[pool.ID]
|
|
if !poolRange.IsSingleIP() {
|
|
return nil, errors.New("only single IP Pools are supported for now")
|
|
}
|
|
addr := poolRange.Addr()
|
|
if _, hasPool := a.poolAllocs[pool.ID]; hasPool {
|
|
if alcID, inUse := a.poolAllocs[pool.ID][addr]; inUse {
|
|
return nil, fmt.Errorf("allocation failed for pool %q, IP %q: %w (allocated to %q)",
|
|
pool.ID, addr, ErrIPAlreadyAllocated, alcID)
|
|
}
|
|
}
|
|
allocIP := &AllocatedIP{
|
|
ProfileID: a.ID,
|
|
PoolID: pool.ID,
|
|
Message: pool.Message,
|
|
Address: addr,
|
|
}
|
|
if dryRun {
|
|
return allocIP, nil
|
|
}
|
|
a.Allocations[allocID] = &PoolAllocation{
|
|
PoolID: pool.ID,
|
|
Address: addr,
|
|
Time: time.Now(),
|
|
}
|
|
if _, hasPool := a.poolAllocs[pool.ID]; !hasPool {
|
|
a.poolAllocs[pool.ID] = make(map[netip.Addr]string)
|
|
}
|
|
a.poolAllocs[pool.ID][addr] = allocID
|
|
return allocIP, nil
|
|
}
|
|
|
|
// removeExpiredUnits removes expired allocations.
|
|
// It stops at first active since TTLIndex is sorted by expiration.
|
|
func (a *IPAllocations) removeExpiredUnits() {
|
|
expiredCount := 0
|
|
for _, allocID := range a.TTLIndex {
|
|
alloc, exists := a.Allocations[allocID]
|
|
if exists && alloc.IsActive(a.prfl.TTL) {
|
|
break
|
|
}
|
|
if alloc != nil {
|
|
if poolMap, hasPool := a.poolAllocs[alloc.PoolID]; hasPool {
|
|
delete(poolMap, alloc.Address)
|
|
}
|
|
}
|
|
delete(a.Allocations, allocID)
|
|
expiredCount++
|
|
}
|
|
if expiredCount > 0 {
|
|
a.TTLIndex = a.TTLIndex[expiredCount:]
|
|
}
|
|
}
|
|
|
|
// removeAllocFromTTLIndex removes an allocationID from TTL index.
|
|
func (a *IPAllocations) removeAllocFromTTLIndex(allocID string) {
|
|
for i, alID := range a.TTLIndex {
|
|
if alID == allocID {
|
|
a.TTLIndex = slices.Delete(a.TTLIndex, i, i+1)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lock acquires a guardian lock on the IPAllocations and stores the lock ID.
|
|
// Uses given lockID (assumes already acquired) or creates a new lock.
|
|
func (a *IPAllocations) Lock(lockID string) {
|
|
if lockID == "" {
|
|
lockID = guardian.Guardian.GuardIDs("",
|
|
0, // TODO: find a way to pass timeout without importing config
|
|
IPAllocationsLockKey(a.Tenant, a.ID))
|
|
}
|
|
a.lockID = lockID
|
|
}
|
|
|
|
// Unlock releases the lock on the IPAllocations and clears the stored lock ID.
|
|
func (a *IPAllocations) Unlock() {
|
|
if a.lockID == "" {
|
|
return
|
|
}
|
|
|
|
// Store current lock ID before clearing to prevent race conditions.
|
|
id := a.lockID
|
|
a.lockID = ""
|
|
guardian.Guardian.UnguardIDs(id)
|
|
|
|
if a.prfl != nil {
|
|
a.prfl.Unlock()
|
|
}
|
|
}
|
|
|
|
// Config returns the IPAllocations' profile configuration.
|
|
func (a *IPAllocations) Config() *IPProfile {
|
|
return a.prfl
|
|
}
|
|
|
|
// AsMapStringInterface converts IPProfile struct to map[string]any
|
|
func (p *IPProfile) AsMapStringInterface() map[string]any {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
return map[string]any{
|
|
Tenant: p.Tenant,
|
|
ID: p.ID,
|
|
FilterIDs: p.FilterIDs,
|
|
Weights: p.Weights,
|
|
TTL: p.TTL,
|
|
Stored: p.Stored,
|
|
Pools: p.Pools,
|
|
}
|
|
}
|
|
|
|
// MapStringInterfaceToIPProfile converts map[string]any to IPProfile struct
|
|
func MapStringInterfaceToIPProfile(m map[string]any) (*IPProfile, error) {
|
|
ipp := &IPProfile{}
|
|
|
|
if v, ok := m[Tenant].(string); ok {
|
|
ipp.Tenant = v
|
|
}
|
|
if v, ok := m[ID].(string); ok {
|
|
ipp.ID = v
|
|
}
|
|
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 {
|
|
return nil, err
|
|
} else {
|
|
ipp.TTL = dur
|
|
}
|
|
} else if v, ok := m[TTL].(float64); ok { // for -1 cases
|
|
ipp.TTL = time.Duration(v)
|
|
}
|
|
if v, ok := m[Stored].(bool); ok {
|
|
ipp.Stored = v
|
|
}
|
|
ipp.Pools = InterfaceToPools(m[Pools])
|
|
return ipp, nil
|
|
}
|
|
|
|
// InterfaceToPools converts any to []*IPPool
|
|
func InterfaceToPools(v any) []*IPPool {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
if pools, ok := v.([]any); ok {
|
|
ipPools := make([]*IPPool, 0, len(pools))
|
|
for _, p := range pools {
|
|
pm, ok := p.(map[string]any)
|
|
if !ok {
|
|
break
|
|
}
|
|
pool := &IPPool{}
|
|
if v, ok := pm[ID].(string); ok {
|
|
pool.ID = v
|
|
}
|
|
|
|
pool.FilterIDs = InterfaceToStringSlice(pm[FilterIDs])
|
|
if v, ok := pm[Type].(string); ok {
|
|
pool.Type = v
|
|
}
|
|
if v, ok := pm[Range].(string); ok {
|
|
pool.Range = v
|
|
}
|
|
if v, ok := pm[Strategy].(string); ok {
|
|
pool.Strategy = v
|
|
}
|
|
if v, ok := pm[Message].(string); ok {
|
|
pool.Message = v
|
|
}
|
|
pool.Weights = InterfaceToDynamicWeights(pm[Weights])
|
|
pool.Blockers = InterfaceToDynamicBlockers(pm[Blockers])
|
|
ipPools = append(ipPools, pool)
|
|
}
|
|
return ipPools
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TenantID returns the unique ID in a multi-tenant environment
|
|
func (a *IPAllocations) TenantID() string {
|
|
return ConcatenatedKey(a.Tenant, a.ID)
|
|
}
|
|
|
|
// CacheClone returns a clone of IPAllocations object used by ltcache CacheCloner.
|
|
func (a *IPAllocations) CacheClone() any {
|
|
return a.Clone()
|
|
}
|
|
|
|
// Clone creates a deep clone of the IPAllocations object (lockID excluded).
|
|
func (a *IPAllocations) Clone() *IPAllocations {
|
|
if a == nil {
|
|
return nil
|
|
}
|
|
clone := &IPAllocations{
|
|
Tenant: a.Tenant,
|
|
ID: a.ID,
|
|
TTLIndex: slices.Clone(a.TTLIndex),
|
|
prfl: a.prfl.Clone(),
|
|
poolRanges: maps.Clone(a.poolRanges),
|
|
}
|
|
if a.poolAllocs != nil {
|
|
clone.poolAllocs = make(map[string]map[netip.Addr]string)
|
|
for poolID, allocs := range a.poolAllocs {
|
|
clone.poolAllocs[poolID] = maps.Clone(allocs)
|
|
}
|
|
}
|
|
if a.Allocations != nil {
|
|
clone.Allocations = make(map[string]*PoolAllocation, len(a.Allocations))
|
|
for id, alloc := range a.Allocations {
|
|
clone.Allocations[id] = alloc.Clone()
|
|
}
|
|
}
|
|
return clone
|
|
}
|
|
|
|
// IPAllocationsLockKey builds the guardian key for locking IP allocations.
|
|
func IPAllocationsLockKey(tnt, id string) string {
|
|
return ConcatenatedKey(CacheIPAllocations, tnt, id)
|
|
}
|
|
|
|
// AsMapStringInterface converts IPAllocations struct to map[string]any
|
|
func (p *IPAllocations) AsMapStringInterface() map[string]any {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
return map[string]any{
|
|
Tenant: p.Tenant,
|
|
ID: p.ID,
|
|
Allocations: p.Allocations,
|
|
TTLIndex: p.TTLIndex,
|
|
}
|
|
}
|
|
|
|
// MapStringInterfaceToIPAllocations converts map[string]any to IPAllocations struct
|
|
func MapStringInterfaceToIPAllocations(m map[string]any) *IPAllocations {
|
|
ipa := &IPAllocations{}
|
|
|
|
if v, ok := m[Tenant].(string); ok {
|
|
ipa.Tenant = v
|
|
}
|
|
if v, ok := m[ID].(string); ok {
|
|
ipa.ID = v
|
|
}
|
|
ipa.Allocations = InterfaceToAllocations(m[Allocations])
|
|
ipa.TTLIndex = InterfaceToStringSlice(m[TTLIndex])
|
|
return ipa
|
|
}
|
|
|
|
// InterfaceToAllocations converts any to map[string]*PoolAllocation
|
|
func InterfaceToAllocations(v any) map[string]*PoolAllocation {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
if allocs, ok := v.(map[string]any); ok {
|
|
ipAllocs := make(map[string]*PoolAllocation)
|
|
for allocID, val := range allocs {
|
|
allocMap, ok := val.(map[string]any)
|
|
if !ok {
|
|
break
|
|
}
|
|
allocation := &PoolAllocation{}
|
|
if v, ok := allocMap[PoolID].(string); ok {
|
|
allocation.PoolID = v
|
|
}
|
|
if v, ok := allocMap[Address].(string); ok {
|
|
if addr, err := netip.ParseAddr(v); err == nil {
|
|
allocation.Address = addr
|
|
}
|
|
}
|
|
if v, ok := allocMap[Time].(string); ok {
|
|
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
|
allocation.Time = t
|
|
}
|
|
}
|
|
ipAllocs[allocID] = allocation
|
|
}
|
|
return ipAllocs
|
|
}
|
|
return nil
|
|
}
|