Dateien
Hetzner-Backup/RESSOURCE_API_PLAN.md
2025-07-03 20:38:33 +00:00

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