Major refactoring of the codebase, including restructuring of files and directories, renaming of modules and classes, and improvements to the overall organization and readability of the code. This refactoring aims to enhance maintainability, scalability, and clarity of the codebase while preserving existing functionality. The changes include:
- Restructuring of the project directory into client and server components - Renaming of modules and classes to better reflect their purpose and functionality - Moving common utilities and configurations to a shared location - Updating import statements to reflect the new structure - Adding new documentation files for better clarity on various aspects of the project - Removing deprecated or unused code to streamline the codebase - Ensuring that all existing functionality is preserved and that the codebase remains functional after the refactoring.
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Demo script for HTTP API endpoints.
|
||||
Tests and demonstrates the plugin data and alert APIs.
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from time import sleep
|
||||
|
||||
BASE_URL = "http://localhost:50004"
|
||||
|
||||
def print_section(title):
|
||||
"""Print a formatted section header."""
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" {title}")
|
||||
print('=' * 70)
|
||||
|
||||
def format_timestamp(timestamp):
|
||||
"""Convert Unix timestamp to readable format."""
|
||||
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
def format_duration(seconds):
|
||||
"""Format duration in human-readable format."""
|
||||
if seconds < 60:
|
||||
return f"{int(seconds)}s"
|
||||
elif seconds < 3600:
|
||||
minutes = int(seconds / 60)
|
||||
secs = int(seconds % 60)
|
||||
return f"{minutes}m {secs}s"
|
||||
elif seconds < 86400:
|
||||
hours = int(seconds / 3600)
|
||||
minutes = int((seconds % 3600) / 60)
|
||||
return f"{hours}h {minutes}m"
|
||||
else:
|
||||
days = int(seconds / 86400)
|
||||
hours = int((seconds % 86400) / 3600)
|
||||
return f"{days}d {hours}h"
|
||||
|
||||
def test_hosts_api():
|
||||
"""Test GET /api/0/hosts endpoint."""
|
||||
print_section("1. List All Monitored Hosts")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/api/0/hosts", timeout=5)
|
||||
response.raise_for_status()
|
||||
hosts = response.json()
|
||||
|
||||
print(f"Found {len(hosts)} hosts:\n")
|
||||
for host in hosts:
|
||||
name = host.get('name', 'unknown')
|
||||
ver = host.get('ver', 0)
|
||||
dyn = host.get('dyn', False)
|
||||
conn_count = len(host.get('connections', []))
|
||||
|
||||
print(f" • {name}")
|
||||
print(f" - Protocol: IPv{ver}")
|
||||
print(f" - Dynamic: {dyn}")
|
||||
print(f" - Connections: {conn_count}")
|
||||
|
||||
return hosts
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return []
|
||||
|
||||
def test_host_plugins_api(hostname):
|
||||
"""Test GET /api/0/hosts/{hostname}/plugins endpoint."""
|
||||
print_section(f"2. Get All Plugins for Host: {hostname}")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/api/0/hosts/{hostname}/plugins", timeout=5)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
plugins = data.get('plugins', {})
|
||||
print(f"Found {len(plugins)} plugins:\n")
|
||||
|
||||
for plugin_name, plugin_data in plugins.items():
|
||||
timestamp = plugin_data.get('timestamp', 0)
|
||||
sample_count = plugin_data.get('sample_count', 0)
|
||||
metrics = plugin_data.get('data', {})
|
||||
|
||||
print(f" 📦 {plugin_name}")
|
||||
print(f" Last update: {format_timestamp(timestamp)}")
|
||||
print(f" Samples: {sample_count}")
|
||||
print(f" Metrics: {len(metrics)}")
|
||||
|
||||
# Show first few metrics
|
||||
for i, (metric, value) in enumerate(metrics.items()):
|
||||
if i < 3: # Show only first 3 metrics
|
||||
if isinstance(value, float):
|
||||
print(f" - {metric}: {value:.2f}")
|
||||
elif isinstance(value, dict):
|
||||
print(f" - {metric}: [nested data, {len(value)} keys]")
|
||||
else:
|
||||
print(f" - {metric}: {value}")
|
||||
|
||||
if len(metrics) > 3:
|
||||
print(f" ... and {len(metrics) - 3} more")
|
||||
print()
|
||||
|
||||
return list(plugins.keys())
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return []
|
||||
|
||||
def test_plugin_detail_api(hostname, plugin_name, limit=5):
|
||||
"""Test GET /api/0/hosts/{hostname}/plugins/{plugin_name} endpoint."""
|
||||
print_section(f"3. Get Detailed Data: {hostname}/{plugin_name}")
|
||||
|
||||
try:
|
||||
url = f"{BASE_URL}/api/0/hosts/{hostname}/plugins/{plugin_name}"
|
||||
params = {'limit': limit}
|
||||
response = requests.get(url, params=params, timeout=5)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
samples = data.get('samples', [])
|
||||
print(f"Retrieved {len(samples)} samples (limit={limit}):\n")
|
||||
|
||||
for i, sample in enumerate(samples):
|
||||
timestamp = sample.get('timestamp', 0)
|
||||
metrics = sample.get('data', {})
|
||||
|
||||
print(f" [{i+1}] {format_timestamp(timestamp)}")
|
||||
for metric, value in sorted(metrics.items())[:5]: # Show first 5 metrics
|
||||
if isinstance(value, float):
|
||||
print(f" {metric}: {value:.2f}")
|
||||
elif isinstance(value, dict):
|
||||
print(f" {metric}: [nested: {len(value)} keys]")
|
||||
else:
|
||||
print(f" {metric}: {value}")
|
||||
print()
|
||||
|
||||
return samples
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return []
|
||||
|
||||
def test_host_alerts_api(hostname):
|
||||
"""Test GET /api/0/hosts/{hostname}/alerts endpoint."""
|
||||
print_section(f"4. Get Alerts for Host: {hostname}")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/api/0/hosts/{hostname}/alerts", timeout=5)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
alerts = data.get('alerts', [])
|
||||
summary = data.get('summary', {})
|
||||
|
||||
print(f"Summary:")
|
||||
print(f" ✓ OK: {summary.get('ok', 0)}")
|
||||
print(f" ⚠️ Warning: {summary.get('warning', 0)}")
|
||||
print(f" 🔴 Critical: {summary.get('critical', 0)}")
|
||||
print(f" ❓ Unknown: {summary.get('unknown', 0)}")
|
||||
print()
|
||||
|
||||
# Show non-OK alerts
|
||||
active_alerts = [a for a in alerts if a.get('level') != 'OK']
|
||||
if active_alerts:
|
||||
print(f"Active Alerts ({len(active_alerts)}):")
|
||||
for alert in active_alerts:
|
||||
metric = alert.get('metric_path', 'unknown')
|
||||
level = alert.get('level', 'UNKNOWN')
|
||||
value = alert.get('last_value', 0)
|
||||
since = alert.get('since', 0)
|
||||
duration = datetime.now().timestamp() - since
|
||||
|
||||
icon = '⚠️' if level == 'WARNING' else '🔴'
|
||||
print(f" {icon} {metric}")
|
||||
print(f" Level: {level}")
|
||||
print(f" Value: {value:.2f}" if isinstance(value, float) else f" Value: {value}")
|
||||
print(f" Duration: {format_duration(duration)}")
|
||||
print()
|
||||
else:
|
||||
print("✓ No active alerts - all systems normal!")
|
||||
|
||||
return data
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return {}
|
||||
|
||||
def test_all_alerts_api():
|
||||
"""Test GET /api/0/alerts endpoint."""
|
||||
print_section("5. Get All Active Alerts Across All Hosts")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/api/0/alerts", timeout=5)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
alerts = data.get('alerts', [])
|
||||
summary = data.get('summary', {})
|
||||
host_count = data.get('host_count', 0)
|
||||
|
||||
print(f"Monitoring {host_count} hosts")
|
||||
print(f"Active Alerts: {summary.get('total', 0)}")
|
||||
print(f" 🔴 Critical: {summary.get('critical', 0)}")
|
||||
print(f" ⚠️ Warning: {summary.get('warning', 0)}")
|
||||
print()
|
||||
|
||||
if alerts:
|
||||
print("Alert Details:")
|
||||
for alert in alerts:
|
||||
hostname = alert.get('hostname', 'unknown')
|
||||
metric = alert.get('metric_path', 'unknown')
|
||||
level = alert.get('level', 'UNKNOWN')
|
||||
value = alert.get('last_value', 0)
|
||||
since = alert.get('since', 0)
|
||||
duration = datetime.now().timestamp() - since
|
||||
notification_count = alert.get('notification_count', 0)
|
||||
|
||||
icon = '⚠️' if level == 'WARNING' else '🔴'
|
||||
print(f" {icon} {hostname} / {metric}")
|
||||
print(f" Level: {level}")
|
||||
print(f" Value: {value:.2f}" if isinstance(value, float) else f" Value: {value}")
|
||||
print(f" Duration: {format_duration(duration)}")
|
||||
print(f" Notifications: {notification_count}")
|
||||
print()
|
||||
else:
|
||||
print("✅ All systems normal - no active alerts!")
|
||||
|
||||
return data
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return {}
|
||||
|
||||
def test_messages_api():
|
||||
"""Test GET /api/0/messages endpoint."""
|
||||
print_section("6. Get Recent Messages")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/api/0/messages", timeout=5)
|
||||
response.raise_for_status()
|
||||
messages = response.json()
|
||||
|
||||
print(f"Last {len(messages)} messages:\n")
|
||||
for msg in messages[-5:]: # Show last 5
|
||||
timestamp = msg.get('time', 0)
|
||||
host = msg.get('host', 'unknown')
|
||||
text = msg.get('msg', '')
|
||||
|
||||
print(f" [{format_timestamp(timestamp)}] {host}: {text}")
|
||||
|
||||
return messages
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return []
|
||||
|
||||
def test_error_handling():
|
||||
"""Test API error handling."""
|
||||
print_section("7. Error Handling Tests")
|
||||
|
||||
# Test non-existent host
|
||||
print("Testing non-existent host...")
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/api/0/hosts/nonexistenthost/plugins", timeout=5)
|
||||
if response.status_code == 404:
|
||||
error_data = response.json()
|
||||
print(f" ✓ Correctly returned 404: {error_data.get('error', 'No error message')}")
|
||||
else:
|
||||
print(f" ⚠️ Unexpected status code: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
# Test non-existent plugin
|
||||
print("\nTesting non-existent plugin...")
|
||||
try:
|
||||
# Get first host
|
||||
hosts = requests.get(f"{BASE_URL}/api/0/hosts", timeout=5).json()
|
||||
if hosts:
|
||||
hostname = hosts[0]['name']
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/api/0/hosts/{hostname}/plugins/nonexistentplugin",
|
||||
timeout=5
|
||||
)
|
||||
if response.status_code == 404:
|
||||
error_data = response.json()
|
||||
print(f" ✓ Correctly returned 404: {error_data.get('error', 'No error message')}")
|
||||
else:
|
||||
print(f" ⚠️ Unexpected status code: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
def demo_monitoring_loop():
|
||||
"""Demonstrate continuous monitoring."""
|
||||
print_section("8. Continuous Monitoring Demo (5 iterations)")
|
||||
|
||||
print("Monitoring alerts every 3 seconds (Ctrl+C to stop)...\n")
|
||||
|
||||
try:
|
||||
for i in range(5):
|
||||
response = requests.get(f"{BASE_URL}/api/0/alerts", timeout=5)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
summary = data.get('summary', {})
|
||||
critical = summary.get('critical', 0)
|
||||
warning = summary.get('warning', 0)
|
||||
|
||||
timestamp = datetime.now().strftime('%H:%M:%S')
|
||||
status = "🔴 CRITICAL" if critical > 0 else "⚠️ WARNING" if warning > 0 else "✅ OK"
|
||||
|
||||
print(f"[{timestamp}] {status} - Critical: {critical}, Warning: {warning}")
|
||||
|
||||
if i < 4: # Don't sleep after last iteration
|
||||
sleep(3)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nMonitoring stopped by user")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
|
||||
def main():
|
||||
"""Run all API tests."""
|
||||
print("""
|
||||
╔══════════════════════════════════════════════════════════════╗
|
||||
║ Heartbeat Daemon HTTP API Demo & Test Suite ║
|
||||
╚══════════════════════════════════════════════════════════════╝
|
||||
""")
|
||||
|
||||
print(f"Testing API at: {BASE_URL}")
|
||||
print(f"Ensure the heartbeat daemon is running!")
|
||||
|
||||
# Test basic connectivity
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/api/0/hosts", timeout=2)
|
||||
response.raise_for_status()
|
||||
print("✅ API is reachable\n")
|
||||
except Exception as e:
|
||||
print(f"❌ Cannot connect to API: {e}")
|
||||
print("\nPlease ensure:")
|
||||
print(" 1. Heartbeat daemon is running")
|
||||
print(" 2. HTTP server is enabled in configuration")
|
||||
print(f" 3. Server is listening on port {BASE_URL.split(':')[-1]}")
|
||||
sys.exit(1)
|
||||
|
||||
# Run test suite
|
||||
hosts = test_hosts_api()
|
||||
|
||||
if not hosts:
|
||||
print("\n⚠️ No hosts found. Ensure clients are sending heartbeats.")
|
||||
return
|
||||
|
||||
# Pick first host for detailed testing
|
||||
hostname = hosts[0].get('name', '')
|
||||
|
||||
if hostname:
|
||||
plugins = test_host_plugins_api(hostname)
|
||||
|
||||
if plugins:
|
||||
# Test detailed plugin data
|
||||
test_plugin_detail_api(hostname, plugins[0], limit=3)
|
||||
|
||||
# Test alert endpoints
|
||||
test_host_alerts_api(hostname)
|
||||
|
||||
# Test global endpoints
|
||||
test_all_alerts_api()
|
||||
test_messages_api()
|
||||
|
||||
# Test error handling
|
||||
test_error_handling()
|
||||
|
||||
# Continuous monitoring demo
|
||||
demo_monitoring_loop()
|
||||
|
||||
print_section("Test Suite Complete")
|
||||
print("""
|
||||
Next Steps:
|
||||
• View the web UI at http://localhost:50004/live
|
||||
• Check plugin metrics at http://localhost:50004/plugins
|
||||
• Monitor alerts at http://localhost:50004/alerts
|
||||
• Read API documentation: docs/HTTP_API.md
|
||||
""")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nDemo interrupted by user")
|
||||
sys.exit(0)
|
||||
Reference in New Issue
Block a user