18 KiB
Resource Management API Implementation Plan
Executive Summary
This plan outlines the implementation of a Dynamic Resource Pool API for the License Server that allows client applications to:
- View available resources from their license's pool
- Reserve resources for their active session
- Automatically release resources when session ends
- Prevent resource conflicts between concurrent sessions
The system works like a "hotel room" model - resources are assigned to a license (the hotel), but individual sessions (guests) reserve specific resources during their stay.
Current State Analysis
Existing Infrastructure
- Database: Complete resource management tables (
resource_pools,license_resources,resource_history) - Admin Panel: Full CRUD operations for resource management
- License Server: No resource endpoints (critical gap)
- Resource Types: domain, ipv4, phone
- Limits: 0-10 resources per type per license
Gap Analysis
- Clients cannot retrieve their assigned resources
- No validation endpoint for resource ownership
- Resource data not included in license activation/verification
Proposed API Architecture
Resource Pool Concept
Key Principles:
- Resources belong to a license pool, but reservation rules differ by type
- Domains: Can be shared (soft reservation) - prefer free, but allow sharing
- IPv4 & Phone: Exclusive use (hard reservation) - one session only
License Pool (Admin-defined)
├── 5 Domains total
│ ├── 2 Used by Session A (can be shared)
│ ├── 1 Used by Session B (can be shared)
│ ├── 1 Used by both A & B (shared)
│ └── 1 Completely free
├── 3 IPv4 Addresses total
│ ├── 1 Reserved by Session A (exclusive)
│ └── 2 Available
└── 2 Phone Numbers total
├── 1 Reserved by Session B (exclusive)
└── 1 Available
Reservation Rules:
- Domains:
prefer_exclusive = true- Try to get unused domain first, but allow sharing if needed - IPv4:
exclusive_only = true- Fail if none available - Phone:
exclusive_only = true- Fail if none available
1. New API Endpoints
GET /api/resources/available
Purpose: Get available (unreserved) resources from the license pool
Request Headers:
X-API-Key: AF-2025-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X-License-Key: XXXX-XXXX-XXXX-XXXX
X-Session-Token: 550e8400-e29b-41d4-a716-446655440000
Response:
{
"success": true,
"pool_status": {
"domains": {
"total": 5,
"reserved": 3,
"available": 2,
"reserved_by_you": 1
},
"ipv4_addresses": {
"total": 3,
"reserved": 1,
"available": 2,
"reserved_by_you": 0
},
"phone_numbers": {
"total": 2,
"reserved": 0,
"available": 2,
"reserved_by_you": 0
}
},
"available_resources": {
"domains": [
{
"id": 124,
"value": "example2.com",
"usage_count": 0, // Completely free
"is_shared": false
},
{
"id": 125,
"value": "example3.com",
"usage_count": 1, // Already used by 1 session
"is_shared": true
}
],
"ipv4_addresses": [
{
"id": 457,
"value": "192.168.1.101"
},
{
"id": 458,
"value": "192.168.1.102"
}
],
"phone_numbers": [
{
"id": 789,
"value": "+49123456789"
},
{
"id": 790,
"value": "+49123456790"
}
]
},
"your_reservations": {
"domains": [
{
"id": 123,
"value": "example.com",
"reserved_at": "2025-01-15T10:00:00Z"
}
]
}
}
POST /api/resources/reserve
Purpose: Reserve specific resources for the current session
Request:
{
"resource_type": "domain",
"resource_id": 124,
"prefer_exclusive": true // Optional, default true for domains
}
Response (Domain - Exclusive):
{
"success": true,
"reservation": {
"resource_id": 124,
"resource_type": "domain",
"resource_value": "example2.com",
"session_token": "550e8400-e29b-41d4-a716-446655440000",
"reserved_at": "2025-01-15T10:30:00Z",
"is_shared": false,
"usage_count": 1
},
"message": "Domain reserved exclusively"
}
Response (Domain - Shared):
{
"success": true,
"reservation": {
"resource_id": 125,
"resource_type": "domain",
"resource_value": "example3.com",
"session_token": "550e8400-e29b-41d4-a716-446655440000",
"reserved_at": "2025-01-15T10:30:00Z",
"is_shared": true,
"usage_count": 2 // Now used by 2 sessions
},
"message": "Domain reserved (shared with 1 other session)"
}
Response (IPv4/Phone - Failed):
{
"success": false,
"error": "Resource already exclusively reserved by another session",
"code": "RESOURCE_UNAVAILABLE"
}
POST /api/resources/release
Purpose: Release a reserved resource (or auto-release on session end)
Request:
{
"resource_id": 124
}
Response:
{
"success": true,
"message": "Resource released successfully"
}
GET /api/resources/my-reservations
Purpose: Get all resources reserved by current session
Response:
{
"success": true,
"reservations": {
"domains": [
{
"id": 123,
"value": "example.com",
"reserved_at": "2025-01-15T10:00:00Z"
}
],
"ipv4_addresses": [],
"phone_numbers": []
}
}
POST /api/resources/validate
Purpose: Validate if a specific resource belongs to the license
Request:
{
"resource_type": "domain",
"resource_value": "example.com"
}
Response:
{
"valid": true,
"resource_id": 123,
"assigned_at": "2025-01-15T10:00:00Z",
"message": "Resource is assigned to your license"
}
GET /api/resources/types
Purpose: Get available resource types and current usage
Response:
{
"resource_types": [
{
"type": "domain",
"display_name": "Domains",
"limit": 5,
"used": 2,
"available": 3,
"validation_pattern": "^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\\.[a-zA-Z]{2,}$"
},
{
"type": "ipv4",
"display_name": "IPv4 Addresses",
"limit": 3,
"used": 1,
"available": 2,
"validation_pattern": "^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"
},
{
"type": "phone",
"display_name": "Phone Numbers",
"limit": 2,
"used": 0,
"available": 2,
"validation_pattern": "^\\+?[0-9]{1,15}$"
}
]
}
2. Enhanced Existing Endpoints
POST /api/license/activate (Enhanced)
Add resource information to activation response:
{
"message": "License activated successfully",
"activation": { ... },
"resources": {
"domains": ["example.com", "test.com"],
"ipv4_addresses": ["192.168.1.100"],
"phone_numbers": []
},
"resource_limits": {
"domain_count": 5,
"ipv4_count": 3,
"phone_count": 2
}
}
POST /api/license/verify (Enhanced)
Include resource summary:
{
"valid": true,
"license": { ... },
"resources_assigned": {
"domains": 2,
"ipv4_addresses": 1,
"phone_numbers": 0
}
}
Implementation Strategy
Phase 1: Database Schema Extension
-
New Table: resource_reservations
CREATE TABLE resource_reservations ( id SERIAL PRIMARY KEY, resource_id INTEGER REFERENCES resource_pools(id) ON DELETE CASCADE, session_token VARCHAR(255) NOT NULL, license_id INTEGER REFERENCES licenses(id) ON DELETE CASCADE, reserved_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(resource_id, session_token) ); CREATE INDEX idx_reservations_session ON resource_reservations(session_token); CREATE INDEX idx_reservations_resource ON resource_reservations(resource_id); -- View for usage counts CREATE VIEW resource_usage_counts AS SELECT rp.id, rp.resource_type, rp.resource_value, COUNT(rr.id) as usage_count, CASE WHEN rp.resource_type = 'domain' AND COUNT(rr.id) > 0 THEN true ELSE false END as is_shared FROM resource_pools rp LEFT JOIN resource_reservations rr ON rp.id = rr.resource_id WHERE rp.status = 'allocated' GROUP BY rp.id, rp.resource_type, rp.resource_value; -
Automatic Cleanup Trigger
-- When session ends, release all reserved resources CREATE OR REPLACE FUNCTION release_session_resources() RETURNS TRIGGER AS $$ BEGIN DELETE FROM resource_reservations WHERE session_token = OLD.session_token; RETURN OLD; END; $$ LANGUAGE plpgsql; CREATE TRIGGER cleanup_session_resources AFTER DELETE ON license_sessions FOR EACH ROW EXECUTE FUNCTION release_session_resources();
Phase 2: License Server Backend (Week 1)
-
Create Resource Models (
v2_lizenzserver/app/models/resource.py)class ResourceReservation(BaseModel): id: int resource_id: int resource_type: ResourceType value: str session_token: str reserved_at: datetime class PoolStatus(BaseModel): total: int reserved: int available: int reserved_by_you: int -
Create Resource Schemas (
v2_lizenzserver/app/schemas/resource.py)class ResourcesResponse(BaseModel): success: bool resources: Dict[str, List[AssignedResource]] limits: Dict[str, int] -
Create Resource Service (
v2_lizenzserver/app/services/resource_service.py)- Query resource_pools and license_resources tables
- Implement caching for performance
- Add resource validation logic
-
Create Resource API Routes (
v2_lizenzserver/app/api/resource.py)- Implement all new endpoints
- Add proper error handling
- Include rate limiting
Phase 2: Security & Authentication (Week 1)
-
Multi-Factor Authentication
- Require API Key + License Key + Session Token
- Validate session is active and belongs to license
- Rate limit: 100 requests/minute per license
-
Data Access Control
- Resources only visible to owning license
- No cross-license data leakage
- Audit log all resource API access
-
Caching Strategy
- Cache resource assignments for 5 minutes
- Invalidate on Admin Panel changes
- Use Redis if available, in-memory fallback
Phase 3: Integration & Testing (Week 2)
-
Integration Tests
- Test all resource endpoints
- Verify security boundaries
- Load test with multiple concurrent requests
-
Client SDK Updates
- Update Python client example
- Update C# client example
- Create resource caching example
-
Documentation
- Update API_REFERENCE.md
- Create resource API examples
- Add troubleshooting guide
Phase 4: Monitoring & Optimization (Week 2)
-
Metrics
- Resource API request count
- Cache hit/miss ratio
- Response time percentiles
-
Performance Optimization
- Database query optimization
- Add indexes if needed
- Implement connection pooling
Security Considerations
-
Authentication Layers
- API Key (system-level)
- License Key (license-level)
- Session Token (session-level)
-
Rate Limiting
- Per-license: 100 req/min
- Per-IP: 1000 req/min
- Burst allowance: 10 requests
-
Data Isolation
- Strict license-based filtering
- No enumeration attacks possible
- Resource IDs not sequential
-
Audit Trail
- Log all resource API access
- Track abnormal access patterns
- Alert on suspicious activity
Client Integration Guide
Python Example
class AccountForgerClient:
def __init__(self):
self.reserved_resources = {}
def get_available_resources(self):
"""Get available resources from the pool"""
headers = self._get_headers()
response = requests.get(
f"{self.base_url}/api/resources/available",
headers=headers
)
return response.json()
def reserve_resource(self, resource_type, resource_id):
"""Reserve a specific resource for this session"""
headers = self._get_headers()
data = {
'resource_type': resource_type,
'resource_id': resource_id
}
response = requests.post(
f"{self.base_url}/api/resources/reserve",
headers=headers,
json=data
)
if response.status_code == 200:
result = response.json()
# Cache locally for quick access
if resource_type not in self.reserved_resources:
self.reserved_resources[resource_type] = []
self.reserved_resources[resource_type].append(result['reservation'])
return response.json()
def release_resource(self, resource_id):
"""Release a reserved resource"""
headers = self._get_headers()
data = {'resource_id': resource_id}
response = requests.post(
f"{self.base_url}/api/resources/release",
headers=headers,
json=data
)
return response.json()
def get_my_reservations(self):
"""Get all my reserved resources"""
headers = self._get_headers()
response = requests.get(
f"{self.base_url}/api/resources/my-reservations",
headers=headers
)
return response.json()
def auto_reserve_resources(self, needed):
"""Automatically reserve needed resources with smart domain selection"""
# Example: needed = {'domains': 2, 'ipv4_addresses': 1}
available = self.get_available_resources()
reserved = {}
for resource_type, count in needed.items():
reserved[resource_type] = []
available_list = available['available_resources'].get(resource_type, [])
if resource_type == 'domain':
# For domains: prefer free ones, but take shared if needed
# Sort by usage_count (free domains first)
sorted_domains = sorted(available_list, key=lambda x: x.get('usage_count', 0))
for domain in sorted_domains[:count]:
result = self.reserve_resource('domain', domain['id'])
if result['success']:
reserved['domains'].append(result['reservation'])
else:
# For IPv4/Phone: only take truly available ones
for i in range(min(count, len(available_list))):
resource = available_list[i]
result = self.reserve_resource(resource_type, resource['id'])
if result['success']:
reserved[resource_type].append(result['reservation'])
else:
# Stop trying if we hit exclusive reservation error
break
return reserved
Resource Reservation Flow Example
# 1. Start session
session = client.start_session(license_key)
# 2. Check available resources
available = client.get_available_resources()
print(f"Available domains: {available['pool_status']['domains']['available']}")
# 3. Reserve what you need
if available['available_resources']['domains']:
domain = available['available_resources']['domains'][0]
client.reserve_resource('domain', domain['id'])
# 4. Use the resources...
# 5. Resources auto-released when session ends
client.end_session() # Triggers automatic cleanup
Handling Resource Conflicts
def reserve_with_retry(client, resource_type, resource_id, max_retries=3):
"""Handle race conditions when multiple sessions reserve simultaneously"""
for attempt in range(max_retries):
try:
result = client.reserve_resource(resource_type, resource_id)
if result['success']:
return result
except Exception as e:
if 'already reserved' in str(e) and attempt < max_retries - 1:
# Get fresh list of available resources
available = client.get_available_resources()
# Pick different resource
continue
raise
return None
Database Migrations
Required Indexes
-- Optimize resource queries
CREATE INDEX idx_license_resources_active_license
ON license_resources(license_id, is_active)
WHERE is_active = TRUE;
CREATE INDEX idx_resource_pools_allocated
ON resource_pools(allocated_to_license, resource_type)
WHERE status = 'allocated';
Rollout Plan
- Week 1: Backend implementation + Security
- Week 2: Testing + Client integration
- Week 3: Staged rollout (10% → 50% → 100%)
- Week 4: Monitoring + Optimization
Success Metrics
- API response time < 100ms (p95)
- Cache hit ratio > 80%
- Zero security incidents
- Client adoption > 90% within 30 days
Risk Mitigation
- Performance Risk: Pre-emptive caching and query optimization
- Security Risk: Multi-layer authentication and rate limiting
- Compatibility Risk: Maintain backward compatibility
- Scalability Risk: Horizontal scaling ready architecture
Future Enhancements
- Webhook Notifications: Notify clients of resource changes
- Resource Usage Analytics: Track actual resource utilization
- Dynamic Resource Allocation: Auto-assign based on usage patterns
- Resource Sharing: Allow resource sharing between licenses