feat: Complete Rust port of WiFi-DensePose with modular crates
Major changes: - Organized Python v1 implementation into v1/ subdirectory - Created Rust workspace with 9 modular crates: - wifi-densepose-core: Core types, traits, errors - wifi-densepose-signal: CSI processing, phase sanitization, FFT - wifi-densepose-nn: Neural network inference (ONNX/Candle/tch) - wifi-densepose-api: Axum-based REST/WebSocket API - wifi-densepose-db: SQLx database layer - wifi-densepose-config: Configuration management - wifi-densepose-hardware: Hardware abstraction - wifi-densepose-wasm: WebAssembly bindings - wifi-densepose-cli: Command-line interface Documentation: - ADR-001: Workspace structure - ADR-002: Signal processing library selection - ADR-003: Neural network inference strategy - DDD domain model with bounded contexts Testing: - 69 tests passing across all crates - Signal processing: 45 tests - Neural networks: 21 tests - Core: 3 doc tests Performance targets: - 10x faster CSI processing (~0.5ms vs ~5ms) - 5x lower memory usage (~100MB vs ~500MB) - WASM support for browser deployment
This commit is contained in:
1967
v1/scripts/api_test_results_20250607_122720.json
Normal file
1967
v1/scripts/api_test_results_20250607_122720.json
Normal file
File diff suppressed because it is too large
Load Diff
1991
v1/scripts/api_test_results_20250607_122856.json
Normal file
1991
v1/scripts/api_test_results_20250607_122856.json
Normal file
File diff suppressed because it is too large
Load Diff
2961
v1/scripts/api_test_results_20250607_123111.json
Normal file
2961
v1/scripts/api_test_results_20250607_123111.json
Normal file
File diff suppressed because it is too large
Load Diff
2269
v1/scripts/api_test_results_20250609_161617.json
Normal file
2269
v1/scripts/api_test_results_20250609_161617.json
Normal file
File diff suppressed because it is too large
Load Diff
2289
v1/scripts/api_test_results_20250609_162928.json
Normal file
2289
v1/scripts/api_test_results_20250609_162928.json
Normal file
File diff suppressed because it is too large
Load Diff
376
v1/scripts/test_api_endpoints.py
Executable file
376
v1/scripts/test_api_endpoints.py
Executable file
@@ -0,0 +1,376 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
API Endpoint Testing Script
|
||||
Tests all WiFi-DensePose API endpoints and provides debugging information.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
import aiohttp
|
||||
import websockets
|
||||
from colorama import Fore, Style, init
|
||||
|
||||
# Initialize colorama for colored output
|
||||
init(autoreset=True)
|
||||
|
||||
class APITester:
|
||||
"""Comprehensive API endpoint tester."""
|
||||
|
||||
def __init__(self, base_url: str = "http://localhost:8000"):
|
||||
self.base_url = base_url
|
||||
self.session = None
|
||||
self.results = {
|
||||
"total_tests": 0,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"errors": [],
|
||||
"test_details": []
|
||||
}
|
||||
|
||||
async def __aenter__(self):
|
||||
"""Async context manager entry."""
|
||||
self.session = aiohttp.ClientSession()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Async context manager exit."""
|
||||
if self.session:
|
||||
await self.session.close()
|
||||
|
||||
def log_success(self, message: str):
|
||||
"""Log success message."""
|
||||
print(f"{Fore.GREEN}✓ {message}{Style.RESET_ALL}")
|
||||
|
||||
def log_error(self, message: str):
|
||||
"""Log error message."""
|
||||
print(f"{Fore.RED}✗ {message}{Style.RESET_ALL}")
|
||||
|
||||
def log_info(self, message: str):
|
||||
"""Log info message."""
|
||||
print(f"{Fore.BLUE}ℹ {message}{Style.RESET_ALL}")
|
||||
|
||||
def log_warning(self, message: str):
|
||||
"""Log warning message."""
|
||||
print(f"{Fore.YELLOW}⚠ {message}{Style.RESET_ALL}")
|
||||
|
||||
async def test_endpoint(
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
expected_status: int = 200,
|
||||
data: Optional[Dict] = None,
|
||||
params: Optional[Dict] = None,
|
||||
headers: Optional[Dict] = None,
|
||||
description: str = ""
|
||||
) -> Dict[str, Any]:
|
||||
"""Test a single API endpoint."""
|
||||
self.results["total_tests"] += 1
|
||||
test_name = f"{method.upper()} {endpoint}"
|
||||
|
||||
try:
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
|
||||
# Prepare request
|
||||
kwargs = {}
|
||||
if data:
|
||||
kwargs["json"] = data
|
||||
if params:
|
||||
kwargs["params"] = params
|
||||
if headers:
|
||||
kwargs["headers"] = headers
|
||||
|
||||
# Make request
|
||||
start_time = time.time()
|
||||
async with self.session.request(method, url, **kwargs) as response:
|
||||
response_time = (time.time() - start_time) * 1000
|
||||
response_text = await response.text()
|
||||
|
||||
# Try to parse JSON response
|
||||
try:
|
||||
response_data = json.loads(response_text) if response_text else {}
|
||||
except json.JSONDecodeError:
|
||||
response_data = {"raw_response": response_text}
|
||||
|
||||
# Check status code
|
||||
status_ok = response.status == expected_status
|
||||
|
||||
test_result = {
|
||||
"test_name": test_name,
|
||||
"description": description,
|
||||
"url": url,
|
||||
"method": method.upper(),
|
||||
"expected_status": expected_status,
|
||||
"actual_status": response.status,
|
||||
"response_time_ms": round(response_time, 2),
|
||||
"response_data": response_data,
|
||||
"success": status_ok,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
if status_ok:
|
||||
self.results["passed"] += 1
|
||||
self.log_success(f"{test_name} - {response.status} ({response_time:.1f}ms)")
|
||||
if description:
|
||||
print(f" {description}")
|
||||
else:
|
||||
self.results["failed"] += 1
|
||||
self.log_error(f"{test_name} - Expected {expected_status}, got {response.status}")
|
||||
if description:
|
||||
print(f" {description}")
|
||||
print(f" Response: {response_text[:200]}...")
|
||||
|
||||
self.results["test_details"].append(test_result)
|
||||
return test_result
|
||||
|
||||
except Exception as e:
|
||||
self.results["failed"] += 1
|
||||
error_msg = f"{test_name} - Exception: {str(e)}"
|
||||
self.log_error(error_msg)
|
||||
|
||||
test_result = {
|
||||
"test_name": test_name,
|
||||
"description": description,
|
||||
"url": f"{self.base_url}{endpoint}",
|
||||
"method": method.upper(),
|
||||
"expected_status": expected_status,
|
||||
"actual_status": None,
|
||||
"response_time_ms": None,
|
||||
"response_data": None,
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc(),
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
self.results["errors"].append(error_msg)
|
||||
self.results["test_details"].append(test_result)
|
||||
return test_result
|
||||
|
||||
async def test_websocket_endpoint(self, endpoint: str, description: str = "") -> Dict[str, Any]:
|
||||
"""Test WebSocket endpoint."""
|
||||
self.results["total_tests"] += 1
|
||||
test_name = f"WebSocket {endpoint}"
|
||||
|
||||
try:
|
||||
ws_url = f"ws://localhost:8000{endpoint}"
|
||||
|
||||
start_time = time.time()
|
||||
async with websockets.connect(ws_url) as websocket:
|
||||
# Send a test message
|
||||
test_message = {"type": "subscribe", "zone_ids": ["zone_1"]}
|
||||
await websocket.send(json.dumps(test_message))
|
||||
|
||||
# Wait for response
|
||||
response = await asyncio.wait_for(websocket.recv(), timeout=3)
|
||||
response_time = (time.time() - start_time) * 1000
|
||||
|
||||
try:
|
||||
response_data = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
response_data = {"raw_response": response}
|
||||
|
||||
test_result = {
|
||||
"test_name": test_name,
|
||||
"description": description,
|
||||
"url": ws_url,
|
||||
"method": "WebSocket",
|
||||
"response_time_ms": round(response_time, 2),
|
||||
"response_data": response_data,
|
||||
"success": True,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
self.results["passed"] += 1
|
||||
self.log_success(f"{test_name} - Connected ({response_time:.1f}ms)")
|
||||
if description:
|
||||
print(f" {description}")
|
||||
|
||||
self.results["test_details"].append(test_result)
|
||||
return test_result
|
||||
|
||||
except Exception as e:
|
||||
self.results["failed"] += 1
|
||||
error_msg = f"{test_name} - Exception: {str(e)}"
|
||||
self.log_error(error_msg)
|
||||
|
||||
test_result = {
|
||||
"test_name": test_name,
|
||||
"description": description,
|
||||
"url": f"ws://localhost:8000{endpoint}",
|
||||
"method": "WebSocket",
|
||||
"response_time_ms": None,
|
||||
"response_data": None,
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc(),
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
self.results["errors"].append(error_msg)
|
||||
self.results["test_details"].append(test_result)
|
||||
return test_result
|
||||
|
||||
async def run_all_tests(self):
|
||||
"""Run all API endpoint tests."""
|
||||
print(f"{Fore.CYAN}{'='*60}")
|
||||
print(f"{Fore.CYAN}WiFi-DensePose API Endpoint Testing")
|
||||
print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}")
|
||||
print()
|
||||
|
||||
# Test Health Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing Health Endpoints:{Style.RESET_ALL}")
|
||||
await self.test_endpoint("GET", "/health/health", description="System health check")
|
||||
await self.test_endpoint("GET", "/health/ready", description="Readiness check")
|
||||
print()
|
||||
|
||||
# Test Pose Estimation Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing Pose Estimation Endpoints:{Style.RESET_ALL}")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/current", description="Current pose estimation")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/current",
|
||||
params={"zone_ids": ["zone_1"], "confidence_threshold": 0.7},
|
||||
description="Current pose estimation with parameters")
|
||||
await self.test_endpoint("POST", "/api/v1/pose/analyze", description="Pose analysis (requires auth)")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/zones/zone_1/occupancy", description="Zone occupancy")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/zones/summary", description="All zones summary")
|
||||
print()
|
||||
|
||||
# Test Historical Data Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing Historical Data Endpoints:{Style.RESET_ALL}")
|
||||
end_time = datetime.now()
|
||||
start_time = end_time - timedelta(hours=1)
|
||||
historical_data = {
|
||||
"start_time": start_time.isoformat(),
|
||||
"end_time": end_time.isoformat(),
|
||||
"zone_ids": ["zone_1"],
|
||||
"aggregation_interval": 300
|
||||
}
|
||||
await self.test_endpoint("POST", "/api/v1/pose/historical",
|
||||
data=historical_data,
|
||||
description="Historical pose data (requires auth)")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/activities", description="Recent activities")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/activities",
|
||||
params={"zone_id": "zone_1", "limit": 5},
|
||||
description="Activities for specific zone")
|
||||
print()
|
||||
|
||||
# Test Calibration Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing Calibration Endpoints:{Style.RESET_ALL}")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/calibration/status", description="Calibration status (requires auth)")
|
||||
await self.test_endpoint("POST", "/api/v1/pose/calibrate", description="Start calibration (requires auth)")
|
||||
print()
|
||||
|
||||
# Test Statistics Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing Statistics Endpoints:{Style.RESET_ALL}")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/stats", description="Pose statistics")
|
||||
await self.test_endpoint("GET", "/api/v1/pose/stats",
|
||||
params={"hours": 12}, description="Pose statistics (12 hours)")
|
||||
print()
|
||||
|
||||
# Test Stream Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing Stream Endpoints:{Style.RESET_ALL}")
|
||||
await self.test_endpoint("GET", "/api/v1/stream/status", description="Stream status")
|
||||
await self.test_endpoint("POST", "/api/v1/stream/start", description="Start streaming (requires auth)")
|
||||
await self.test_endpoint("POST", "/api/v1/stream/stop", description="Stop streaming (requires auth)")
|
||||
print()
|
||||
|
||||
# Test WebSocket Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing WebSocket Endpoints:{Style.RESET_ALL}")
|
||||
await self.test_websocket_endpoint("/api/v1/stream/pose", description="Pose WebSocket")
|
||||
await self.test_websocket_endpoint("/api/v1/stream/events", description="Events WebSocket")
|
||||
print()
|
||||
|
||||
# Test Documentation Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing Documentation Endpoints:{Style.RESET_ALL}")
|
||||
await self.test_endpoint("GET", "/docs", description="API documentation")
|
||||
await self.test_endpoint("GET", "/openapi.json", description="OpenAPI schema")
|
||||
print()
|
||||
|
||||
# Test API Info Endpoints
|
||||
print(f"{Fore.MAGENTA}Testing API Info Endpoints:{Style.RESET_ALL}")
|
||||
await self.test_endpoint("GET", "/", description="Root endpoint")
|
||||
await self.test_endpoint("GET", "/api/v1/info", description="API information")
|
||||
await self.test_endpoint("GET", "/api/v1/status", description="API status")
|
||||
print()
|
||||
|
||||
# Test Error Cases
|
||||
print(f"{Fore.MAGENTA}Testing Error Cases:{Style.RESET_ALL}")
|
||||
await self.test_endpoint("GET", "/nonexistent", expected_status=404,
|
||||
description="Non-existent endpoint")
|
||||
await self.test_endpoint("POST", "/api/v1/pose/analyze",
|
||||
data={"invalid": "data"}, expected_status=401,
|
||||
description="Unauthorized request (no auth)")
|
||||
print()
|
||||
|
||||
def print_summary(self):
|
||||
"""Print test summary."""
|
||||
print(f"{Fore.CYAN}{'='*60}")
|
||||
print(f"{Fore.CYAN}Test Summary")
|
||||
print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}")
|
||||
|
||||
total = self.results["total_tests"]
|
||||
passed = self.results["passed"]
|
||||
failed = self.results["failed"]
|
||||
success_rate = (passed / total * 100) if total > 0 else 0
|
||||
|
||||
print(f"Total Tests: {total}")
|
||||
print(f"{Fore.GREEN}Passed: {passed}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}Failed: {failed}{Style.RESET_ALL}")
|
||||
print(f"Success Rate: {success_rate:.1f}%")
|
||||
print()
|
||||
|
||||
if self.results["errors"]:
|
||||
print(f"{Fore.RED}Errors:{Style.RESET_ALL}")
|
||||
for error in self.results["errors"]:
|
||||
print(f" - {error}")
|
||||
print()
|
||||
|
||||
# Save detailed results to file
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
results_file = f"scripts/api_test_results_{timestamp}.json"
|
||||
|
||||
try:
|
||||
with open(results_file, 'w') as f:
|
||||
json.dump(self.results, f, indent=2, default=str)
|
||||
print(f"Detailed results saved to: {results_file}")
|
||||
except Exception as e:
|
||||
self.log_warning(f"Could not save results file: {e}")
|
||||
|
||||
return failed == 0
|
||||
|
||||
async def main():
|
||||
"""Main test function."""
|
||||
try:
|
||||
async with APITester() as tester:
|
||||
await tester.run_all_tests()
|
||||
success = tester.print_summary()
|
||||
|
||||
# Exit with appropriate code
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{Fore.YELLOW}Tests interrupted by user{Style.RESET_ALL}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n{Fore.RED}Fatal error: {e}{Style.RESET_ALL}")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check if required packages are available
|
||||
try:
|
||||
import aiohttp
|
||||
import websockets
|
||||
import colorama
|
||||
except ImportError as e:
|
||||
print(f"Missing required package: {e}")
|
||||
print("Install with: pip install aiohttp websockets colorama")
|
||||
sys.exit(1)
|
||||
|
||||
# Run tests
|
||||
asyncio.run(main())
|
||||
366
v1/scripts/test_monitoring.py
Executable file
366
v1/scripts/test_monitoring.py
Executable file
@@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for WiFi-DensePose monitoring functionality
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List
|
||||
import time
|
||||
|
||||
|
||||
class MonitoringTester:
|
||||
"""Test monitoring endpoints and metrics collection."""
|
||||
|
||||
def __init__(self, base_url: str = "http://localhost:8000"):
|
||||
self.base_url = base_url
|
||||
self.session = None
|
||||
self.results = []
|
||||
|
||||
async def setup(self):
|
||||
"""Setup test session."""
|
||||
self.session = aiohttp.ClientSession()
|
||||
|
||||
async def teardown(self):
|
||||
"""Cleanup test session."""
|
||||
if self.session:
|
||||
await self.session.close()
|
||||
|
||||
async def test_health_endpoint(self):
|
||||
"""Test the /health endpoint."""
|
||||
print("\n[TEST] Health Endpoint")
|
||||
try:
|
||||
async with self.session.get(f"{self.base_url}/health") as response:
|
||||
status = response.status
|
||||
data = await response.json()
|
||||
|
||||
print(f"Status: {status}")
|
||||
print(f"Response: {json.dumps(data, indent=2)}")
|
||||
|
||||
self.results.append({
|
||||
"test": "health_endpoint",
|
||||
"status": "passed" if status == 200 else "failed",
|
||||
"response_code": status,
|
||||
"data": data
|
||||
})
|
||||
|
||||
# Verify structure
|
||||
assert "status" in data
|
||||
assert "timestamp" in data
|
||||
assert "components" in data
|
||||
assert "system_metrics" in data
|
||||
|
||||
print("✅ Health endpoint test passed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Health endpoint test failed: {e}")
|
||||
self.results.append({
|
||||
"test": "health_endpoint",
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
async def test_ready_endpoint(self):
|
||||
"""Test the /ready endpoint."""
|
||||
print("\n[TEST] Readiness Endpoint")
|
||||
try:
|
||||
async with self.session.get(f"{self.base_url}/ready") as response:
|
||||
status = response.status
|
||||
data = await response.json()
|
||||
|
||||
print(f"Status: {status}")
|
||||
print(f"Response: {json.dumps(data, indent=2)}")
|
||||
|
||||
self.results.append({
|
||||
"test": "ready_endpoint",
|
||||
"status": "passed" if status == 200 else "failed",
|
||||
"response_code": status,
|
||||
"data": data
|
||||
})
|
||||
|
||||
# Verify structure
|
||||
assert "ready" in data
|
||||
assert "timestamp" in data
|
||||
assert "checks" in data
|
||||
assert "message" in data
|
||||
|
||||
print("✅ Readiness endpoint test passed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Readiness endpoint test failed: {e}")
|
||||
self.results.append({
|
||||
"test": "ready_endpoint",
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
async def test_liveness_endpoint(self):
|
||||
"""Test the /live endpoint."""
|
||||
print("\n[TEST] Liveness Endpoint")
|
||||
try:
|
||||
async with self.session.get(f"{self.base_url}/live") as response:
|
||||
status = response.status
|
||||
data = await response.json()
|
||||
|
||||
print(f"Status: {status}")
|
||||
print(f"Response: {json.dumps(data, indent=2)}")
|
||||
|
||||
self.results.append({
|
||||
"test": "liveness_endpoint",
|
||||
"status": "passed" if status == 200 else "failed",
|
||||
"response_code": status,
|
||||
"data": data
|
||||
})
|
||||
|
||||
# Verify structure
|
||||
assert "status" in data
|
||||
assert "timestamp" in data
|
||||
|
||||
print("✅ Liveness endpoint test passed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Liveness endpoint test failed: {e}")
|
||||
self.results.append({
|
||||
"test": "liveness_endpoint",
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
async def test_metrics_endpoint(self):
|
||||
"""Test the /metrics endpoint."""
|
||||
print("\n[TEST] Metrics Endpoint")
|
||||
try:
|
||||
async with self.session.get(f"{self.base_url}/metrics") as response:
|
||||
status = response.status
|
||||
data = await response.json()
|
||||
|
||||
print(f"Status: {status}")
|
||||
print(f"Response: {json.dumps(data, indent=2)}")
|
||||
|
||||
self.results.append({
|
||||
"test": "metrics_endpoint",
|
||||
"status": "passed" if status == 200 else "failed",
|
||||
"response_code": status,
|
||||
"data": data
|
||||
})
|
||||
|
||||
# Verify structure
|
||||
assert "timestamp" in data
|
||||
assert "metrics" in data
|
||||
|
||||
# Check for system metrics
|
||||
metrics = data.get("metrics", {})
|
||||
assert "cpu" in metrics
|
||||
assert "memory" in metrics
|
||||
assert "disk" in metrics
|
||||
assert "network" in metrics
|
||||
|
||||
print("✅ Metrics endpoint test passed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Metrics endpoint test failed: {e}")
|
||||
self.results.append({
|
||||
"test": "metrics_endpoint",
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
async def test_version_endpoint(self):
|
||||
"""Test the /version endpoint."""
|
||||
print("\n[TEST] Version Endpoint")
|
||||
try:
|
||||
async with self.session.get(f"{self.base_url}/version") as response:
|
||||
status = response.status
|
||||
data = await response.json()
|
||||
|
||||
print(f"Status: {status}")
|
||||
print(f"Response: {json.dumps(data, indent=2)}")
|
||||
|
||||
self.results.append({
|
||||
"test": "version_endpoint",
|
||||
"status": "passed" if status == 200 else "failed",
|
||||
"response_code": status,
|
||||
"data": data
|
||||
})
|
||||
|
||||
# Verify structure
|
||||
assert "name" in data
|
||||
assert "version" in data
|
||||
assert "environment" in data
|
||||
assert "timestamp" in data
|
||||
|
||||
print("✅ Version endpoint test passed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Version endpoint test failed: {e}")
|
||||
self.results.append({
|
||||
"test": "version_endpoint",
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
async def test_metrics_collection(self):
|
||||
"""Test metrics collection over time."""
|
||||
print("\n[TEST] Metrics Collection Over Time")
|
||||
try:
|
||||
# Collect metrics 3 times with 2-second intervals
|
||||
metrics_snapshots = []
|
||||
|
||||
for i in range(3):
|
||||
async with self.session.get(f"{self.base_url}/metrics") as response:
|
||||
data = await response.json()
|
||||
metrics_snapshots.append({
|
||||
"timestamp": time.time(),
|
||||
"metrics": data.get("metrics", {})
|
||||
})
|
||||
|
||||
if i < 2:
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Verify metrics are changing
|
||||
cpu_values = [
|
||||
snapshot["metrics"].get("cpu", {}).get("percent", 0)
|
||||
for snapshot in metrics_snapshots
|
||||
]
|
||||
|
||||
print(f"CPU usage over time: {cpu_values}")
|
||||
|
||||
# Check if at least some metrics are non-zero
|
||||
all_zeros = all(v == 0 for v in cpu_values)
|
||||
assert not all_zeros, "All CPU metrics are zero"
|
||||
|
||||
self.results.append({
|
||||
"test": "metrics_collection",
|
||||
"status": "passed",
|
||||
"snapshots": len(metrics_snapshots),
|
||||
"cpu_values": cpu_values
|
||||
})
|
||||
|
||||
print("✅ Metrics collection test passed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Metrics collection test failed: {e}")
|
||||
self.results.append({
|
||||
"test": "metrics_collection",
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
async def test_system_load(self):
|
||||
"""Test system under load to verify monitoring."""
|
||||
print("\n[TEST] System Load Monitoring")
|
||||
try:
|
||||
# Generate some load by making multiple concurrent requests
|
||||
print("Generating load with 20 concurrent requests...")
|
||||
|
||||
tasks = []
|
||||
for i in range(20):
|
||||
tasks.append(self.session.get(f"{self.base_url}/health"))
|
||||
|
||||
start_time = time.time()
|
||||
responses = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
duration = time.time() - start_time
|
||||
|
||||
success_count = sum(
|
||||
1 for r in responses
|
||||
if not isinstance(r, Exception) and r.status == 200
|
||||
)
|
||||
|
||||
print(f"Completed {len(responses)} requests in {duration:.2f}s")
|
||||
print(f"Success rate: {success_count}/{len(responses)}")
|
||||
|
||||
# Check metrics after load
|
||||
async with self.session.get(f"{self.base_url}/metrics") as response:
|
||||
data = await response.json()
|
||||
metrics = data.get("metrics", {})
|
||||
|
||||
print(f"CPU after load: {metrics.get('cpu', {}).get('percent', 0)}%")
|
||||
print(f"Memory usage: {metrics.get('memory', {}).get('percent', 0)}%")
|
||||
|
||||
self.results.append({
|
||||
"test": "system_load",
|
||||
"status": "passed",
|
||||
"requests": len(responses),
|
||||
"success_rate": f"{success_count}/{len(responses)}",
|
||||
"duration": duration
|
||||
})
|
||||
|
||||
print("✅ System load monitoring test passed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ System load monitoring test failed: {e}")
|
||||
self.results.append({
|
||||
"test": "system_load",
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
async def run_all_tests(self):
|
||||
"""Run all monitoring tests."""
|
||||
print("=== WiFi-DensePose Monitoring Tests ===")
|
||||
print(f"Base URL: {self.base_url}")
|
||||
print(f"Started at: {datetime.now().isoformat()}")
|
||||
|
||||
await self.setup()
|
||||
|
||||
try:
|
||||
# Run all tests
|
||||
await self.test_health_endpoint()
|
||||
await self.test_ready_endpoint()
|
||||
await self.test_liveness_endpoint()
|
||||
await self.test_metrics_endpoint()
|
||||
await self.test_version_endpoint()
|
||||
await self.test_metrics_collection()
|
||||
await self.test_system_load()
|
||||
|
||||
finally:
|
||||
await self.teardown()
|
||||
|
||||
# Print summary
|
||||
print("\n=== Test Summary ===")
|
||||
passed = sum(1 for r in self.results if r["status"] == "passed")
|
||||
failed = sum(1 for r in self.results if r["status"] == "failed")
|
||||
|
||||
print(f"Total tests: {len(self.results)}")
|
||||
print(f"Passed: {passed}")
|
||||
print(f"Failed: {failed}")
|
||||
|
||||
if failed > 0:
|
||||
print("\nFailed tests:")
|
||||
for result in self.results:
|
||||
if result["status"] == "failed":
|
||||
print(f" - {result['test']}: {result.get('error', 'Unknown error')}")
|
||||
|
||||
# Save results
|
||||
with open("monitoring_test_results.json", "w") as f:
|
||||
json.dump({
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"base_url": self.base_url,
|
||||
"summary": {
|
||||
"total": len(self.results),
|
||||
"passed": passed,
|
||||
"failed": failed
|
||||
},
|
||||
"results": self.results
|
||||
}, f, indent=2)
|
||||
|
||||
print("\nResults saved to monitoring_test_results.json")
|
||||
|
||||
return failed == 0
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point."""
|
||||
base_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8000"
|
||||
|
||||
tester = MonitoringTester(base_url)
|
||||
success = await tester.run_all_tests()
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
157
v1/scripts/test_websocket_streaming.py
Executable file
157
v1/scripts/test_websocket_streaming.py
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
WebSocket Streaming Test Script
|
||||
Tests real-time pose data streaming via WebSocket
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import websockets
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
async def test_pose_streaming():
|
||||
"""Test pose data streaming via WebSocket."""
|
||||
uri = "ws://localhost:8000/api/v1/stream/pose?zone_ids=zone_1,zone_2&min_confidence=0.3&max_fps=10"
|
||||
|
||||
print(f"[{datetime.now()}] Connecting to WebSocket...")
|
||||
|
||||
try:
|
||||
async with websockets.connect(uri) as websocket:
|
||||
print(f"[{datetime.now()}] Connected successfully!")
|
||||
|
||||
# Wait for connection confirmation
|
||||
response = await websocket.recv()
|
||||
data = json.loads(response)
|
||||
print(f"[{datetime.now()}] Connection confirmed:")
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
# Send a ping message
|
||||
ping_msg = {"type": "ping"}
|
||||
await websocket.send(json.dumps(ping_msg))
|
||||
print(f"[{datetime.now()}] Sent ping message")
|
||||
|
||||
# Listen for messages for 10 seconds
|
||||
print(f"[{datetime.now()}] Listening for pose updates...")
|
||||
|
||||
start_time = asyncio.get_event_loop().time()
|
||||
message_count = 0
|
||||
|
||||
while asyncio.get_event_loop().time() - start_time < 10:
|
||||
try:
|
||||
# Wait for message with timeout
|
||||
message = await asyncio.wait_for(websocket.recv(), timeout=1.0)
|
||||
data = json.loads(message)
|
||||
message_count += 1
|
||||
|
||||
msg_type = data.get("type", "unknown")
|
||||
|
||||
if msg_type == "pose_update":
|
||||
print(f"[{datetime.now()}] Pose update received:")
|
||||
print(f" - Frame ID: {data.get('frame_id')}")
|
||||
print(f" - Persons detected: {len(data.get('persons', []))}")
|
||||
print(f" - Zone summary: {data.get('zone_summary', {})}")
|
||||
elif msg_type == "pong":
|
||||
print(f"[{datetime.now()}] Pong received")
|
||||
else:
|
||||
print(f"[{datetime.now()}] Message type '{msg_type}' received")
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
# No message received in timeout period
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now()}] Error receiving message: {e}")
|
||||
|
||||
print(f"\n[{datetime.now()}] Test completed!")
|
||||
print(f"Total messages received: {message_count}")
|
||||
|
||||
# Send disconnect message
|
||||
disconnect_msg = {"type": "disconnect"}
|
||||
await websocket.send(json.dumps(disconnect_msg))
|
||||
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now()}] WebSocket error: {e}")
|
||||
|
||||
|
||||
async def test_event_streaming():
|
||||
"""Test event streaming via WebSocket."""
|
||||
uri = "ws://localhost:8000/api/v1/stream/events?event_types=motion,presence&zone_ids=zone_1"
|
||||
|
||||
print(f"\n[{datetime.now()}] Testing event streaming...")
|
||||
print(f"[{datetime.now()}] Connecting to WebSocket...")
|
||||
|
||||
try:
|
||||
async with websockets.connect(uri) as websocket:
|
||||
print(f"[{datetime.now()}] Connected successfully!")
|
||||
|
||||
# Wait for connection confirmation
|
||||
response = await websocket.recv()
|
||||
data = json.loads(response)
|
||||
print(f"[{datetime.now()}] Connection confirmed:")
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
# Get status
|
||||
status_msg = {"type": "get_status"}
|
||||
await websocket.send(json.dumps(status_msg))
|
||||
print(f"[{datetime.now()}] Requested status")
|
||||
|
||||
# Listen for a few messages
|
||||
for i in range(5):
|
||||
try:
|
||||
message = await asyncio.wait_for(websocket.recv(), timeout=2.0)
|
||||
data = json.loads(message)
|
||||
print(f"[{datetime.now()}] Event received: {data.get('type')}")
|
||||
except asyncio.TimeoutError:
|
||||
print(f"[{datetime.now()}] No event received (timeout)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now()}] WebSocket error: {e}")
|
||||
|
||||
|
||||
async def test_websocket_errors():
|
||||
"""Test WebSocket error handling."""
|
||||
print(f"\n[{datetime.now()}] Testing error handling...")
|
||||
|
||||
# Test invalid endpoint
|
||||
try:
|
||||
uri = "ws://localhost:8000/api/v1/stream/invalid"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
print("Connected to invalid endpoint (unexpected)")
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now()}] Expected error for invalid endpoint: {type(e).__name__}")
|
||||
|
||||
# Test sending invalid JSON
|
||||
try:
|
||||
uri = "ws://localhost:8000/api/v1/stream/pose"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
await websocket.send("invalid json {")
|
||||
response = await websocket.recv()
|
||||
data = json.loads(response)
|
||||
if data.get("type") == "error":
|
||||
print(f"[{datetime.now()}] Received expected error for invalid JSON")
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now()}] Error testing invalid JSON: {e}")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run all WebSocket tests."""
|
||||
print("=" * 60)
|
||||
print("WiFi-DensePose WebSocket Streaming Tests")
|
||||
print("=" * 60)
|
||||
|
||||
# Test pose streaming
|
||||
await test_pose_streaming()
|
||||
|
||||
# Test event streaming
|
||||
await test_event_streaming()
|
||||
|
||||
# Test error handling
|
||||
await test_websocket_errors()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("All tests completed!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
398
v1/scripts/validate-deployment.sh
Executable file
398
v1/scripts/validate-deployment.sh
Executable file
@@ -0,0 +1,398 @@
|
||||
#!/bin/bash
|
||||
|
||||
# WiFi-DensePose Deployment Validation Script
|
||||
# This script validates that all deployment components are functioning correctly
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
NAMESPACE="wifi-densepose"
|
||||
MONITORING_NAMESPACE="monitoring"
|
||||
TIMEOUT=300
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if kubectl is available and configured
|
||||
check_kubectl() {
|
||||
log_info "Checking kubectl configuration..."
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
log_error "kubectl is not installed or not in PATH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! kubectl cluster-info &> /dev/null; then
|
||||
log_error "kubectl is not configured or cluster is not accessible"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "kubectl is configured and cluster is accessible"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate namespace exists
|
||||
validate_namespace() {
|
||||
local ns=$1
|
||||
log_info "Validating namespace: $ns"
|
||||
|
||||
if kubectl get namespace "$ns" &> /dev/null; then
|
||||
log_success "Namespace $ns exists"
|
||||
return 0
|
||||
else
|
||||
log_error "Namespace $ns does not exist"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate deployments are ready
|
||||
validate_deployments() {
|
||||
log_info "Validating deployments in namespace: $NAMESPACE"
|
||||
|
||||
local deployments
|
||||
deployments=$(kubectl get deployments -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}')
|
||||
|
||||
if [ -z "$deployments" ]; then
|
||||
log_warning "No deployments found in namespace $NAMESPACE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local failed=0
|
||||
for deployment in $deployments; do
|
||||
log_info "Checking deployment: $deployment"
|
||||
|
||||
if kubectl wait --for=condition=available --timeout="${TIMEOUT}s" "deployment/$deployment" -n "$NAMESPACE" &> /dev/null; then
|
||||
local ready_replicas
|
||||
ready_replicas=$(kubectl get deployment "$deployment" -n "$NAMESPACE" -o jsonpath='{.status.readyReplicas}')
|
||||
local desired_replicas
|
||||
desired_replicas=$(kubectl get deployment "$deployment" -n "$NAMESPACE" -o jsonpath='{.spec.replicas}')
|
||||
|
||||
if [ "$ready_replicas" = "$desired_replicas" ]; then
|
||||
log_success "Deployment $deployment is ready ($ready_replicas/$desired_replicas replicas)"
|
||||
else
|
||||
log_warning "Deployment $deployment has $ready_replicas/$desired_replicas replicas ready"
|
||||
failed=1
|
||||
fi
|
||||
else
|
||||
log_error "Deployment $deployment is not ready within ${TIMEOUT}s"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
return $failed
|
||||
}
|
||||
|
||||
# Validate services are accessible
|
||||
validate_services() {
|
||||
log_info "Validating services in namespace: $NAMESPACE"
|
||||
|
||||
local services
|
||||
services=$(kubectl get services -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}')
|
||||
|
||||
if [ -z "$services" ]; then
|
||||
log_warning "No services found in namespace $NAMESPACE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local failed=0
|
||||
for service in $services; do
|
||||
log_info "Checking service: $service"
|
||||
|
||||
local endpoints
|
||||
endpoints=$(kubectl get endpoints "$service" -n "$NAMESPACE" -o jsonpath='{.subsets[*].addresses[*].ip}')
|
||||
|
||||
if [ -n "$endpoints" ]; then
|
||||
log_success "Service $service has endpoints: $endpoints"
|
||||
else
|
||||
log_error "Service $service has no endpoints"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
return $failed
|
||||
}
|
||||
|
||||
# Validate ingress configuration
|
||||
validate_ingress() {
|
||||
log_info "Validating ingress configuration in namespace: $NAMESPACE"
|
||||
|
||||
local ingresses
|
||||
ingresses=$(kubectl get ingress -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}')
|
||||
|
||||
if [ -z "$ingresses" ]; then
|
||||
log_warning "No ingress resources found in namespace $NAMESPACE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local failed=0
|
||||
for ingress in $ingresses; do
|
||||
log_info "Checking ingress: $ingress"
|
||||
|
||||
local hosts
|
||||
hosts=$(kubectl get ingress "$ingress" -n "$NAMESPACE" -o jsonpath='{.spec.rules[*].host}')
|
||||
|
||||
if [ -n "$hosts" ]; then
|
||||
log_success "Ingress $ingress configured for hosts: $hosts"
|
||||
|
||||
# Check if ingress has an IP/hostname assigned
|
||||
local address
|
||||
address=$(kubectl get ingress "$ingress" -n "$NAMESPACE" -o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}')
|
||||
|
||||
if [ -n "$address" ]; then
|
||||
log_success "Ingress $ingress has address: $address"
|
||||
else
|
||||
log_warning "Ingress $ingress does not have an assigned address yet"
|
||||
fi
|
||||
else
|
||||
log_error "Ingress $ingress has no configured hosts"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
return $failed
|
||||
}
|
||||
|
||||
# Validate ConfigMaps and Secrets
|
||||
validate_config() {
|
||||
log_info "Validating ConfigMaps and Secrets in namespace: $NAMESPACE"
|
||||
|
||||
# Check ConfigMaps
|
||||
local configmaps
|
||||
configmaps=$(kubectl get configmaps -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}')
|
||||
|
||||
if [ -n "$configmaps" ]; then
|
||||
log_success "ConfigMaps found: $configmaps"
|
||||
else
|
||||
log_warning "No ConfigMaps found in namespace $NAMESPACE"
|
||||
fi
|
||||
|
||||
# Check Secrets
|
||||
local secrets
|
||||
secrets=$(kubectl get secrets -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}' | tr ' ' '\n' | grep -v "default-token" | tr '\n' ' ')
|
||||
|
||||
if [ -n "$secrets" ]; then
|
||||
log_success "Secrets found: $secrets"
|
||||
else
|
||||
log_warning "No custom secrets found in namespace $NAMESPACE"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate HPA configuration
|
||||
validate_hpa() {
|
||||
log_info "Validating Horizontal Pod Autoscaler in namespace: $NAMESPACE"
|
||||
|
||||
local hpas
|
||||
hpas=$(kubectl get hpa -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}')
|
||||
|
||||
if [ -z "$hpas" ]; then
|
||||
log_warning "No HPA resources found in namespace $NAMESPACE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local failed=0
|
||||
for hpa in $hpas; do
|
||||
log_info "Checking HPA: $hpa"
|
||||
|
||||
local current_replicas
|
||||
current_replicas=$(kubectl get hpa "$hpa" -n "$NAMESPACE" -o jsonpath='{.status.currentReplicas}')
|
||||
local desired_replicas
|
||||
desired_replicas=$(kubectl get hpa "$hpa" -n "$NAMESPACE" -o jsonpath='{.status.desiredReplicas}')
|
||||
|
||||
if [ -n "$current_replicas" ] && [ -n "$desired_replicas" ]; then
|
||||
log_success "HPA $hpa: current=$current_replicas, desired=$desired_replicas"
|
||||
else
|
||||
log_warning "HPA $hpa metrics not available yet"
|
||||
fi
|
||||
done
|
||||
|
||||
return $failed
|
||||
}
|
||||
|
||||
# Test application health endpoints
|
||||
test_health_endpoints() {
|
||||
log_info "Testing application health endpoints..."
|
||||
|
||||
# Get application pods
|
||||
local pods
|
||||
pods=$(kubectl get pods -n "$NAMESPACE" -l app=wifi-densepose -o jsonpath='{.items[*].metadata.name}')
|
||||
|
||||
if [ -z "$pods" ]; then
|
||||
log_error "No application pods found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local failed=0
|
||||
for pod in $pods; do
|
||||
log_info "Testing health endpoint for pod: $pod"
|
||||
|
||||
# Port forward and test health endpoint
|
||||
kubectl port-forward "pod/$pod" 8080:8080 -n "$NAMESPACE" &
|
||||
local pf_pid=$!
|
||||
sleep 2
|
||||
|
||||
if curl -f http://localhost:8080/health &> /dev/null; then
|
||||
log_success "Health endpoint for pod $pod is responding"
|
||||
else
|
||||
log_error "Health endpoint for pod $pod is not responding"
|
||||
failed=1
|
||||
fi
|
||||
|
||||
kill $pf_pid 2>/dev/null || true
|
||||
sleep 1
|
||||
done
|
||||
|
||||
return $failed
|
||||
}
|
||||
|
||||
# Validate monitoring stack
|
||||
validate_monitoring() {
|
||||
log_info "Validating monitoring stack in namespace: $MONITORING_NAMESPACE"
|
||||
|
||||
if ! validate_namespace "$MONITORING_NAMESPACE"; then
|
||||
log_warning "Monitoring namespace not found, skipping monitoring validation"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check Prometheus
|
||||
if kubectl get deployment prometheus-server -n "$MONITORING_NAMESPACE" &> /dev/null; then
|
||||
if kubectl wait --for=condition=available --timeout=60s deployment/prometheus-server -n "$MONITORING_NAMESPACE" &> /dev/null; then
|
||||
log_success "Prometheus is running"
|
||||
else
|
||||
log_error "Prometheus is not ready"
|
||||
fi
|
||||
else
|
||||
log_warning "Prometheus deployment not found"
|
||||
fi
|
||||
|
||||
# Check Grafana
|
||||
if kubectl get deployment grafana -n "$MONITORING_NAMESPACE" &> /dev/null; then
|
||||
if kubectl wait --for=condition=available --timeout=60s deployment/grafana -n "$MONITORING_NAMESPACE" &> /dev/null; then
|
||||
log_success "Grafana is running"
|
||||
else
|
||||
log_error "Grafana is not ready"
|
||||
fi
|
||||
else
|
||||
log_warning "Grafana deployment not found"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate logging stack
|
||||
validate_logging() {
|
||||
log_info "Validating logging stack..."
|
||||
|
||||
# Check Fluentd DaemonSet
|
||||
if kubectl get daemonset fluentd -n kube-system &> /dev/null; then
|
||||
local desired
|
||||
desired=$(kubectl get daemonset fluentd -n kube-system -o jsonpath='{.status.desiredNumberScheduled}')
|
||||
local ready
|
||||
ready=$(kubectl get daemonset fluentd -n kube-system -o jsonpath='{.status.numberReady}')
|
||||
|
||||
if [ "$desired" = "$ready" ]; then
|
||||
log_success "Fluentd DaemonSet is ready ($ready/$desired nodes)"
|
||||
else
|
||||
log_warning "Fluentd DaemonSet has $ready/$desired pods ready"
|
||||
fi
|
||||
else
|
||||
log_warning "Fluentd DaemonSet not found"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check resource usage
|
||||
check_resource_usage() {
|
||||
log_info "Checking resource usage..."
|
||||
|
||||
# Check node resource usage
|
||||
log_info "Node resource usage:"
|
||||
kubectl top nodes 2>/dev/null || log_warning "Metrics server not available for node metrics"
|
||||
|
||||
# Check pod resource usage
|
||||
log_info "Pod resource usage in namespace $NAMESPACE:"
|
||||
kubectl top pods -n "$NAMESPACE" 2>/dev/null || log_warning "Metrics server not available for pod metrics"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Generate validation report
|
||||
generate_report() {
|
||||
local total_checks=$1
|
||||
local failed_checks=$2
|
||||
local passed_checks=$((total_checks - failed_checks))
|
||||
|
||||
echo ""
|
||||
log_info "=== Deployment Validation Report ==="
|
||||
echo "Total checks: $total_checks"
|
||||
echo "Passed: $passed_checks"
|
||||
echo "Failed: $failed_checks"
|
||||
|
||||
if [ $failed_checks -eq 0 ]; then
|
||||
log_success "All validation checks passed! 🎉"
|
||||
return 0
|
||||
else
|
||||
log_error "Some validation checks failed. Please review the output above."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main validation function
|
||||
main() {
|
||||
log_info "Starting WiFi-DensePose deployment validation..."
|
||||
|
||||
local total_checks=0
|
||||
local failed_checks=0
|
||||
|
||||
# Run validation checks
|
||||
checks=(
|
||||
"check_kubectl"
|
||||
"validate_namespace $NAMESPACE"
|
||||
"validate_deployments"
|
||||
"validate_services"
|
||||
"validate_ingress"
|
||||
"validate_config"
|
||||
"validate_hpa"
|
||||
"test_health_endpoints"
|
||||
"validate_monitoring"
|
||||
"validate_logging"
|
||||
"check_resource_usage"
|
||||
)
|
||||
|
||||
for check in "${checks[@]}"; do
|
||||
total_checks=$((total_checks + 1))
|
||||
echo ""
|
||||
if ! eval "$check"; then
|
||||
failed_checks=$((failed_checks + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate final report
|
||||
generate_report $total_checks $failed_checks
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
458
v1/scripts/validate-integration.sh
Executable file
458
v1/scripts/validate-integration.sh
Executable file
@@ -0,0 +1,458 @@
|
||||
#!/bin/bash
|
||||
|
||||
# WiFi-DensePose Integration Validation Script
|
||||
# This script validates the complete system integration
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
VENV_PATH="${PROJECT_ROOT}/.venv"
|
||||
TEST_DB_PATH="${PROJECT_ROOT}/test_integration.db"
|
||||
LOG_FILE="${PROJECT_ROOT}/integration_validation.log"
|
||||
|
||||
# Functions
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}✅ $1${NC}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}❌ $1${NC}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
log "Cleaning up test resources..."
|
||||
|
||||
# Stop any running servers
|
||||
pkill -f "wifi-densepose" || true
|
||||
pkill -f "uvicorn.*src.app" || true
|
||||
|
||||
# Remove test database
|
||||
[ -f "$TEST_DB_PATH" ] && rm -f "$TEST_DB_PATH"
|
||||
|
||||
# Remove test logs
|
||||
find "$PROJECT_ROOT" -name "*.log" -path "*/test*" -delete 2>/dev/null || true
|
||||
|
||||
success "Cleanup completed"
|
||||
}
|
||||
|
||||
check_prerequisites() {
|
||||
log "Checking prerequisites..."
|
||||
|
||||
# Check Python version
|
||||
if ! python3 --version | grep -E "Python 3\.(9|10|11|12)" > /dev/null; then
|
||||
error "Python 3.9+ is required"
|
||||
exit 1
|
||||
fi
|
||||
success "Python version check passed"
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [ ! -d "$VENV_PATH" ]; then
|
||||
warning "Virtual environment not found, creating one..."
|
||||
python3 -m venv "$VENV_PATH"
|
||||
fi
|
||||
success "Virtual environment check passed"
|
||||
|
||||
# Activate virtual environment
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Check if requirements are installed
|
||||
if ! pip list | grep -q "fastapi"; then
|
||||
warning "Dependencies not installed, installing..."
|
||||
pip install -e ".[dev]"
|
||||
fi
|
||||
success "Dependencies check passed"
|
||||
}
|
||||
|
||||
validate_package_structure() {
|
||||
log "Validating package structure..."
|
||||
|
||||
# Check main application files
|
||||
required_files=(
|
||||
"src/__init__.py"
|
||||
"src/main.py"
|
||||
"src/app.py"
|
||||
"src/config.py"
|
||||
"src/logger.py"
|
||||
"src/cli.py"
|
||||
"pyproject.toml"
|
||||
"setup.py"
|
||||
"MANIFEST.in"
|
||||
)
|
||||
|
||||
for file in "${required_files[@]}"; do
|
||||
if [ ! -f "$PROJECT_ROOT/$file" ]; then
|
||||
error "Required file missing: $file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
success "Package structure validation passed"
|
||||
|
||||
# Check directory structure
|
||||
required_dirs=(
|
||||
"src/config"
|
||||
"src/core"
|
||||
"src/api"
|
||||
"src/services"
|
||||
"src/middleware"
|
||||
"src/database"
|
||||
"src/tasks"
|
||||
"src/commands"
|
||||
"tests/unit"
|
||||
"tests/integration"
|
||||
)
|
||||
|
||||
for dir in "${required_dirs[@]}"; do
|
||||
if [ ! -d "$PROJECT_ROOT/$dir" ]; then
|
||||
error "Required directory missing: $dir"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
success "Directory structure validation passed"
|
||||
}
|
||||
|
||||
validate_imports() {
|
||||
log "Validating Python imports..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Test main package import
|
||||
if ! python -c "import src; print(f'Package version: {src.__version__}')"; then
|
||||
error "Failed to import main package"
|
||||
exit 1
|
||||
fi
|
||||
success "Main package import passed"
|
||||
|
||||
# Test core components
|
||||
core_modules=(
|
||||
"src.app"
|
||||
"src.config.settings"
|
||||
"src.logger"
|
||||
"src.cli"
|
||||
"src.core.csi_processor"
|
||||
"src.core.phase_sanitizer"
|
||||
"src.core.pose_estimator"
|
||||
"src.core.router_interface"
|
||||
"src.services.orchestrator"
|
||||
"src.database.connection"
|
||||
"src.database.models"
|
||||
)
|
||||
|
||||
for module in "${core_modules[@]}"; do
|
||||
if ! python -c "import $module" 2>/dev/null; then
|
||||
error "Failed to import module: $module"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
success "Core modules import passed"
|
||||
}
|
||||
|
||||
validate_configuration() {
|
||||
log "Validating configuration..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Test configuration loading
|
||||
if ! python -c "
|
||||
from src.config.settings import get_settings
|
||||
settings = get_settings()
|
||||
print(f'Environment: {settings.environment}')
|
||||
print(f'Debug: {settings.debug}')
|
||||
print(f'API Version: {settings.api_version}')
|
||||
"; then
|
||||
error "Configuration validation failed"
|
||||
exit 1
|
||||
fi
|
||||
success "Configuration validation passed"
|
||||
}
|
||||
|
||||
validate_database() {
|
||||
log "Validating database integration..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Test database connection and models
|
||||
if ! python -c "
|
||||
import asyncio
|
||||
from src.config.settings import get_settings
|
||||
from src.database.connection import get_database_manager
|
||||
|
||||
async def test_db():
|
||||
settings = get_settings()
|
||||
settings.database_url = 'sqlite+aiosqlite:///test_integration.db'
|
||||
|
||||
db_manager = get_database_manager(settings)
|
||||
await db_manager.initialize()
|
||||
await db_manager.test_connection()
|
||||
|
||||
# Test connection stats
|
||||
stats = await db_manager.get_connection_stats()
|
||||
print(f'Database connected: {stats[\"database\"][\"connected\"]}')
|
||||
|
||||
await db_manager.close_all_connections()
|
||||
print('Database validation passed')
|
||||
|
||||
asyncio.run(test_db())
|
||||
"; then
|
||||
error "Database validation failed"
|
||||
exit 1
|
||||
fi
|
||||
success "Database validation passed"
|
||||
}
|
||||
|
||||
validate_api_endpoints() {
|
||||
log "Validating API endpoints..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Start server in background
|
||||
export WIFI_DENSEPOSE_ENVIRONMENT=test
|
||||
export WIFI_DENSEPOSE_DATABASE_URL="sqlite+aiosqlite:///test_integration.db"
|
||||
|
||||
python -m uvicorn src.app:app --host 127.0.0.1 --port 8888 --log-level error &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Wait for server to start
|
||||
sleep 5
|
||||
|
||||
# Test endpoints
|
||||
endpoints=(
|
||||
"http://127.0.0.1:8888/health"
|
||||
"http://127.0.0.1:8888/metrics"
|
||||
"http://127.0.0.1:8888/api/v1/devices"
|
||||
"http://127.0.0.1:8888/api/v1/sessions"
|
||||
)
|
||||
|
||||
for endpoint in "${endpoints[@]}"; do
|
||||
if ! curl -s -f "$endpoint" > /dev/null; then
|
||||
error "API endpoint failed: $endpoint"
|
||||
kill $SERVER_PID 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Stop server
|
||||
kill $SERVER_PID 2>/dev/null || true
|
||||
wait $SERVER_PID 2>/dev/null || true
|
||||
|
||||
success "API endpoints validation passed"
|
||||
}
|
||||
|
||||
validate_cli() {
|
||||
log "Validating CLI interface..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Test CLI commands
|
||||
if ! python -m src.cli --help > /dev/null; then
|
||||
error "CLI help command failed"
|
||||
exit 1
|
||||
fi
|
||||
success "CLI help command passed"
|
||||
|
||||
# Test version command
|
||||
if ! python -m src.cli version > /dev/null; then
|
||||
error "CLI version command failed"
|
||||
exit 1
|
||||
fi
|
||||
success "CLI version command passed"
|
||||
|
||||
# Test config validation
|
||||
export WIFI_DENSEPOSE_ENVIRONMENT=test
|
||||
export WIFI_DENSEPOSE_DATABASE_URL="sqlite+aiosqlite:///test_integration.db"
|
||||
|
||||
if ! python -m src.cli config validate > /dev/null; then
|
||||
error "CLI config validation failed"
|
||||
exit 1
|
||||
fi
|
||||
success "CLI config validation passed"
|
||||
}
|
||||
|
||||
validate_background_tasks() {
|
||||
log "Validating background tasks..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Test task managers
|
||||
if ! python -c "
|
||||
import asyncio
|
||||
from src.config.settings import get_settings
|
||||
from src.tasks.cleanup import get_cleanup_manager
|
||||
from src.tasks.monitoring import get_monitoring_manager
|
||||
from src.tasks.backup import get_backup_manager
|
||||
|
||||
async def test_tasks():
|
||||
settings = get_settings()
|
||||
settings.database_url = 'sqlite+aiosqlite:///test_integration.db'
|
||||
|
||||
# Test cleanup manager
|
||||
cleanup_manager = get_cleanup_manager(settings)
|
||||
cleanup_stats = cleanup_manager.get_stats()
|
||||
print(f'Cleanup manager initialized: {\"manager\" in cleanup_stats}')
|
||||
|
||||
# Test monitoring manager
|
||||
monitoring_manager = get_monitoring_manager(settings)
|
||||
monitoring_stats = monitoring_manager.get_stats()
|
||||
print(f'Monitoring manager initialized: {\"manager\" in monitoring_stats}')
|
||||
|
||||
# Test backup manager
|
||||
backup_manager = get_backup_manager(settings)
|
||||
backup_stats = backup_manager.get_stats()
|
||||
print(f'Backup manager initialized: {\"manager\" in backup_stats}')
|
||||
|
||||
print('Background tasks validation passed')
|
||||
|
||||
asyncio.run(test_tasks())
|
||||
"; then
|
||||
error "Background tasks validation failed"
|
||||
exit 1
|
||||
fi
|
||||
success "Background tasks validation passed"
|
||||
}
|
||||
|
||||
run_integration_tests() {
|
||||
log "Running integration tests..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Set test environment
|
||||
export WIFI_DENSEPOSE_ENVIRONMENT=test
|
||||
export WIFI_DENSEPOSE_DATABASE_URL="sqlite+aiosqlite:///test_integration.db"
|
||||
|
||||
# Run integration tests
|
||||
if ! python -m pytest tests/integration/ -v --tb=short; then
|
||||
error "Integration tests failed"
|
||||
exit 1
|
||||
fi
|
||||
success "Integration tests passed"
|
||||
}
|
||||
|
||||
validate_package_build() {
|
||||
log "Validating package build..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# Install build tools
|
||||
pip install build twine
|
||||
|
||||
# Build package
|
||||
if ! python -m build; then
|
||||
error "Package build failed"
|
||||
exit 1
|
||||
fi
|
||||
success "Package build passed"
|
||||
|
||||
# Check package
|
||||
if ! python -m twine check dist/*; then
|
||||
error "Package check failed"
|
||||
exit 1
|
||||
fi
|
||||
success "Package check passed"
|
||||
|
||||
# Clean up build artifacts
|
||||
rm -rf build/ dist/ *.egg-info/
|
||||
}
|
||||
|
||||
generate_report() {
|
||||
log "Generating integration report..."
|
||||
|
||||
cat > "$PROJECT_ROOT/integration_report.md" << EOF
|
||||
# WiFi-DensePose Integration Validation Report
|
||||
|
||||
**Date:** $(date)
|
||||
**Status:** ✅ PASSED
|
||||
|
||||
## Validation Results
|
||||
|
||||
### Prerequisites
|
||||
- ✅ Python version check
|
||||
- ✅ Virtual environment setup
|
||||
- ✅ Dependencies installation
|
||||
|
||||
### Package Structure
|
||||
- ✅ Required files present
|
||||
- ✅ Directory structure valid
|
||||
- ✅ Python imports working
|
||||
|
||||
### Core Components
|
||||
- ✅ Configuration management
|
||||
- ✅ Database integration
|
||||
- ✅ API endpoints
|
||||
- ✅ CLI interface
|
||||
- ✅ Background tasks
|
||||
|
||||
### Testing
|
||||
- ✅ Integration tests passed
|
||||
- ✅ Package build successful
|
||||
|
||||
## System Information
|
||||
|
||||
**Python Version:** $(python --version)
|
||||
**Package Version:** $(python -c "import src; print(src.__version__)")
|
||||
**Environment:** $(python -c "from src.config.settings import get_settings; print(get_settings().environment)")
|
||||
|
||||
## Next Steps
|
||||
|
||||
The WiFi-DensePose system has been successfully integrated and validated.
|
||||
You can now:
|
||||
|
||||
1. Start the server: \`wifi-densepose start\`
|
||||
2. Check status: \`wifi-densepose status\`
|
||||
3. View configuration: \`wifi-densepose config show\`
|
||||
4. Run tests: \`pytest tests/\`
|
||||
|
||||
For more information, see the documentation in the \`docs/\` directory.
|
||||
EOF
|
||||
|
||||
success "Integration report generated: integration_report.md"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "Starting WiFi-DensePose integration validation..."
|
||||
|
||||
# Trap cleanup on exit
|
||||
trap cleanup EXIT
|
||||
|
||||
# Run validation steps
|
||||
check_prerequisites
|
||||
validate_package_structure
|
||||
validate_imports
|
||||
validate_configuration
|
||||
validate_database
|
||||
validate_api_endpoints
|
||||
validate_cli
|
||||
validate_background_tasks
|
||||
run_integration_tests
|
||||
validate_package_build
|
||||
generate_report
|
||||
|
||||
success "🎉 All integration validations passed!"
|
||||
log "Integration validation completed successfully"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user