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

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:

  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:

{
  "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

  1. 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;
    
  2. 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)

  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
    
  2. Create Resource Schemas (v2_lizenzserver/app/schemas/resource.py)

    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

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

  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