652 Zeilen
18 KiB
Markdown
652 Zeilen
18 KiB
Markdown
# 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:
|
|
1. View available resources from their license's pool
|
|
2. Reserve resources for their active session
|
|
3. Automatically release resources when session ends
|
|
4. 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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"resource_type": "domain",
|
|
"resource_id": 124,
|
|
"prefer_exclusive": true // Optional, default true for domains
|
|
}
|
|
```
|
|
|
|
**Response (Domain - Exclusive):**
|
|
```json
|
|
{
|
|
"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):**
|
|
```json
|
|
{
|
|
"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):**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"resource_id": 124
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Resource released successfully"
|
|
}
|
|
```
|
|
|
|
#### GET /api/resources/my-reservations
|
|
**Purpose**: Get all resources reserved by current session
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"resource_type": "domain",
|
|
"resource_value": "example.com"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"valid": true,
|
|
"license": { ... },
|
|
"resources_assigned": {
|
|
"domains": 2,
|
|
"ipv4_addresses": 1,
|
|
"phone_numbers": 0
|
|
}
|
|
}
|
|
```
|
|
|
|
## Implementation Strategy
|
|
|
|
### Phase 1: Database Schema Extension
|
|
|
|
1. **New Table: resource_reservations**
|
|
```sql
|
|
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;
|
|
```
|
|
|
|
2. **Automatic Cleanup Trigger**
|
|
```sql
|
|
-- 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)
|
|
|
|
1. **Create Resource Models** (`v2_lizenzserver/app/models/resource.py`)
|
|
```python
|
|
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
|
|
```
|
|
|
|
2. **Create Resource Schemas** (`v2_lizenzserver/app/schemas/resource.py`)
|
|
```python
|
|
class ResourcesResponse(BaseModel):
|
|
success: bool
|
|
resources: Dict[str, List[AssignedResource]]
|
|
limits: Dict[str, int]
|
|
```
|
|
|
|
3. **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
|
|
|
|
4. **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)
|
|
|
|
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
|
|
|
|
2. **Data Access Control**
|
|
- Resources only visible to owning license
|
|
- No cross-license data leakage
|
|
- Audit log all resource API access
|
|
|
|
3. **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)
|
|
|
|
1. **Integration Tests**
|
|
- Test all resource endpoints
|
|
- Verify security boundaries
|
|
- Load test with multiple concurrent requests
|
|
|
|
2. **Client SDK Updates**
|
|
- Update Python client example
|
|
- Update C# client example
|
|
- Create resource caching example
|
|
|
|
3. **Documentation**
|
|
- Update API_REFERENCE.md
|
|
- Create resource API examples
|
|
- Add troubleshooting guide
|
|
|
|
### Phase 4: Monitoring & Optimization (Week 2)
|
|
|
|
1. **Metrics**
|
|
- Resource API request count
|
|
- Cache hit/miss ratio
|
|
- Response time percentiles
|
|
|
|
2. **Performance Optimization**
|
|
- Database query optimization
|
|
- Add indexes if needed
|
|
- Implement connection pooling
|
|
|
|
## Security Considerations
|
|
|
|
1. **Authentication Layers**
|
|
- API Key (system-level)
|
|
- License Key (license-level)
|
|
- Session Token (session-level)
|
|
|
|
2. **Rate Limiting**
|
|
- Per-license: 100 req/min
|
|
- Per-IP: 1000 req/min
|
|
- Burst allowance: 10 requests
|
|
|
|
3. **Data Isolation**
|
|
- Strict license-based filtering
|
|
- No enumeration attacks possible
|
|
- Resource IDs not sequential
|
|
|
|
4. **Audit Trail**
|
|
- Log all resource API access
|
|
- Track abnormal access patterns
|
|
- Alert on suspicious activity
|
|
|
|
## Client Integration Guide
|
|
|
|
### Python Example
|
|
```python
|
|
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
|
|
```python
|
|
# 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
|
|
```python
|
|
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
|
|
```sql
|
|
-- 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
|
|
|
|
1. **Week 1**: Backend implementation + Security
|
|
2. **Week 2**: Testing + Client integration
|
|
3. **Week 3**: Staged rollout (10% → 50% → 100%)
|
|
4. **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
|
|
|
|
1. **Performance Risk**: Pre-emptive caching and query optimization
|
|
2. **Security Risk**: Multi-layer authentication and rate limiting
|
|
3. **Compatibility Risk**: Maintain backward compatibility
|
|
4. **Scalability Risk**: Horizontal scaling ready architecture
|
|
|
|
## Future Enhancements
|
|
|
|
1. **Webhook Notifications**: Notify clients of resource changes
|
|
2. **Resource Usage Analytics**: Track actual resource utilization
|
|
3. **Dynamic Resource Allocation**: Auto-assign based on usage patterns
|
|
4. **Resource Sharing**: Allow resource sharing between licenses |