Move blockchain app READMEs to centralized documentation
Some checks failed
API Endpoint Tests / test-api-endpoints (push) Successful in 10s
Blockchain Synchronization Verification / sync-verification (push) Failing after 3s
CLI Tests / test-cli (push) Failing after 4s
Documentation Validation / validate-docs (push) Successful in 8s
Documentation Validation / validate-policies-strict (push) Successful in 4s
Integration Tests / test-service-integration (push) Successful in 38s
Multi-Node Blockchain Health Monitoring / health-check (push) Successful in 2s
P2P Network Verification / p2p-verification (push) Successful in 3s
Security Scanning / security-scan (push) Successful in 40s
Smart Contract Tests / test-solidity (map[name:aitbc-token path:packages/solidity/aitbc-token]) (push) Successful in 15s
Smart Contract Tests / lint-solidity (push) Successful in 8s
Some checks failed
API Endpoint Tests / test-api-endpoints (push) Successful in 10s
Blockchain Synchronization Verification / sync-verification (push) Failing after 3s
CLI Tests / test-cli (push) Failing after 4s
Documentation Validation / validate-docs (push) Successful in 8s
Documentation Validation / validate-policies-strict (push) Successful in 4s
Integration Tests / test-service-integration (push) Successful in 38s
Multi-Node Blockchain Health Monitoring / health-check (push) Successful in 2s
P2P Network Verification / p2p-verification (push) Successful in 3s
Security Scanning / security-scan (push) Successful in 40s
Smart Contract Tests / test-solidity (map[name:aitbc-token path:packages/solidity/aitbc-token]) (push) Successful in 15s
Smart Contract Tests / lint-solidity (push) Successful in 8s
- Relocate blockchain-event-bridge README content to docs/apps/blockchain/blockchain-event-bridge.md - Relocate blockchain-explorer README content to docs/apps/blockchain/blockchain-explorer.md - Replace app READMEs with redirect notices pointing to new documentation location - Consolidate documentation in central docs/ directory for better organization
This commit is contained in:
349
cli/commands/blockchain_event_bridge.py
Normal file
349
cli/commands/blockchain_event_bridge.py
Normal file
@@ -0,0 +1,349 @@
|
||||
"""
|
||||
Blockchain Event Bridge CLI Commands for AITBC
|
||||
Commands for managing blockchain event bridge service
|
||||
"""
|
||||
|
||||
import click
|
||||
import json
|
||||
import requests
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
@click.group()
|
||||
def bridge():
|
||||
"""Blockchain event bridge management commands"""
|
||||
pass
|
||||
|
||||
@bridge.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def health(test_mode):
|
||||
"""Health check for blockchain event bridge service"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_health = {
|
||||
"status": "healthy",
|
||||
"service": "blockchain-event-bridge",
|
||||
"version": "0.1.0",
|
||||
"uptime_seconds": 86400,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
click.echo("🏥 Blockchain Event Bridge Health:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"✅ Status: {mock_health['status']}")
|
||||
click.echo(f"📦 Service: {mock_health['service']}")
|
||||
click.echo(f"📦 Version: {mock_health['version']}")
|
||||
click.echo(f"⏱️ Uptime: {mock_health['uptime_seconds']}s")
|
||||
click.echo(f"🕐 Timestamp: {mock_health['timestamp']}")
|
||||
return
|
||||
|
||||
# Fetch from bridge service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.bridge_url}/health",
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
health = response.json()
|
||||
|
||||
click.echo("🏥 Blockchain Event Bridge Health:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"✅ Status: {health.get('status', 'unknown')}")
|
||||
click.echo(f"📦 Service: {health.get('service', 'unknown')}")
|
||||
click.echo(f"📦 Version: {health.get('version', 'unknown')}")
|
||||
click.echo(f"⏱️ Uptime: {health.get('uptime_seconds', 0)}s")
|
||||
click.echo(f"🕐 Timestamp: {health.get('timestamp', 'unknown')}")
|
||||
else:
|
||||
click.echo(f"❌ Health check failed: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error checking health: {str(e)}", err=True)
|
||||
|
||||
@bridge.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def metrics(test_mode):
|
||||
"""Get Prometheus metrics from blockchain event bridge service"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_metrics = """
|
||||
# HELP bridge_events_total Total number of blockchain events processed
|
||||
# TYPE bridge_events_total counter
|
||||
bridge_events_total{type="block"} 12345
|
||||
bridge_events_total{type="transaction"} 67890
|
||||
bridge_events_total{type="contract"} 23456
|
||||
# HELP bridge_events_processed_total Total number of events successfully processed
|
||||
# TYPE bridge_events_processed_total counter
|
||||
bridge_events_processed_total 103691
|
||||
# HELP bridge_events_failed_total Total number of events that failed processing
|
||||
# TYPE bridge_events_failed_total counter
|
||||
bridge_events_failed_total 123
|
||||
# HELP bridge_processing_duration_seconds Event processing duration
|
||||
# TYPE bridge_processing_duration_seconds histogram
|
||||
bridge_processing_duration_seconds_bucket{le="0.1"} 50000
|
||||
bridge_processing_duration_seconds_bucket{le="1.0"} 100000
|
||||
bridge_processing_duration_seconds_sum 45000.5
|
||||
bridge_processing_duration_seconds_count 103691
|
||||
""".strip()
|
||||
|
||||
click.echo("📊 Prometheus Metrics:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(mock_metrics)
|
||||
return
|
||||
|
||||
# Fetch from bridge service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.bridge_url}/metrics",
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
metrics = response.text
|
||||
click.echo("📊 Prometheus Metrics:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(metrics)
|
||||
else:
|
||||
click.echo(f"❌ Failed to get metrics: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting metrics: {str(e)}", err=True)
|
||||
|
||||
@bridge.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def status(test_mode):
|
||||
"""Get detailed status of blockchain event bridge service"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_status = {
|
||||
"service": "blockchain-event-bridge",
|
||||
"status": "running",
|
||||
"version": "0.1.0",
|
||||
"subscriptions": {
|
||||
"blocks": {
|
||||
"enabled": True,
|
||||
"topic": "blocks",
|
||||
"last_block": 123456
|
||||
},
|
||||
"transactions": {
|
||||
"enabled": True,
|
||||
"topic": "transactions",
|
||||
"last_transaction": "0xabc123..."
|
||||
},
|
||||
"contract_events": {
|
||||
"enabled": True,
|
||||
"contracts": [
|
||||
"AgentStaking",
|
||||
"PerformanceVerifier",
|
||||
"AgentServiceMarketplace"
|
||||
],
|
||||
"last_event": "0xdef456..."
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"agent_daemon": {
|
||||
"enabled": True,
|
||||
"events_triggered": 5432
|
||||
},
|
||||
"coordinator_api": {
|
||||
"enabled": True,
|
||||
"events_triggered": 8765
|
||||
},
|
||||
"marketplace": {
|
||||
"enabled": True,
|
||||
"events_triggered": 3210
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"events_processed": 103691,
|
||||
"events_failed": 123,
|
||||
"success_rate": 99.88
|
||||
}
|
||||
}
|
||||
|
||||
click.echo("📊 Blockchain Event Bridge Status:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"📦 Service: {mock_status['service']}")
|
||||
click.echo(f"✅ Status: {mock_status['status']}")
|
||||
click.echo(f"📦 Version: {mock_status['version']}")
|
||||
click.echo("")
|
||||
click.echo("🔔 Subscriptions:")
|
||||
for sub_type, sub_data in mock_status['subscriptions'].items():
|
||||
click.echo(f" {sub_type}:")
|
||||
click.echo(f" Enabled: {sub_data['enabled']}")
|
||||
if 'topic' in sub_data:
|
||||
click.echo(f" Topic: {sub_data['topic']}")
|
||||
if 'last_block' in sub_data:
|
||||
click.echo(f" Last Block: {sub_data['last_block']}")
|
||||
if 'contracts' in sub_data:
|
||||
click.echo(f" Contracts: {', '.join(sub_data['contracts'])}")
|
||||
click.echo("")
|
||||
click.echo("🎯 Triggers:")
|
||||
for trigger_type, trigger_data in mock_status['triggers'].items():
|
||||
click.echo(f" {trigger_type}:")
|
||||
click.echo(f" Enabled: {trigger_data['enabled']}")
|
||||
click.echo(f" Events Triggered: {trigger_data['events_triggered']}")
|
||||
click.echo("")
|
||||
click.echo("📊 Metrics:")
|
||||
click.echo(f" Events Processed: {mock_status['metrics']['events_processed']}")
|
||||
click.echo(f" Events Failed: {mock_status['metrics']['events_failed']}")
|
||||
click.echo(f" Success Rate: {mock_status['metrics']['success_rate']}%")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from bridge service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.bridge_url}/",
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
status = response.json()
|
||||
|
||||
click.echo("📊 Blockchain Event Bridge Status:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"📦 Service: {status.get('service', 'unknown')}")
|
||||
click.echo(f"✅ Status: {status.get('status', 'unknown')}")
|
||||
click.echo(f"📦 Version: {status.get('version', 'unknown')}")
|
||||
|
||||
if 'subscriptions' in status:
|
||||
click.echo("")
|
||||
click.echo("🔔 Subscriptions:")
|
||||
for sub_type, sub_data in status['subscriptions'].items():
|
||||
click.echo(f" {sub_type}:")
|
||||
click.echo(f" Enabled: {sub_data.get('enabled', False)}")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get status: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting status: {str(e)}", err=True)
|
||||
|
||||
@bridge.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def config(test_mode):
|
||||
"""Show current configuration of blockchain event bridge service"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_config = {
|
||||
"blockchain_rpc_url": "http://localhost:8006",
|
||||
"gossip_backend": "redis",
|
||||
"gossip_broadcast_url": "redis://localhost:6379",
|
||||
"coordinator_api_url": "http://localhost:8011",
|
||||
"coordinator_api_key": "***",
|
||||
"subscriptions": {
|
||||
"blocks": True,
|
||||
"transactions": True
|
||||
},
|
||||
"triggers": {
|
||||
"agent_daemon": True,
|
||||
"coordinator_api": True,
|
||||
"marketplace": True
|
||||
},
|
||||
"polling": {
|
||||
"enabled": False,
|
||||
"interval_seconds": 60
|
||||
}
|
||||
}
|
||||
|
||||
click.echo("⚙️ Blockchain Event Bridge Configuration:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"🔗 Blockchain RPC URL: {mock_config['blockchain_rpc_url']}")
|
||||
click.echo(f"💬 Gossip Backend: {mock_config['gossip_backend']}")
|
||||
if mock_config.get('gossip_broadcast_url'):
|
||||
click.echo(f"📡 Gossip Broadcast URL: {mock_config['gossip_broadcast_url']}")
|
||||
click.echo(f"🎯 Coordinator API URL: {mock_config['coordinator_api_url']}")
|
||||
click.echo(f"🔑 Coordinator API Key: {mock_config['coordinator_api_key']}")
|
||||
click.echo("")
|
||||
click.echo("🔔 Subscriptions:")
|
||||
for sub, enabled in mock_config['subscriptions'].items():
|
||||
status = "✅" if enabled else "❌"
|
||||
click.echo(f" {status} {sub}")
|
||||
click.echo("")
|
||||
click.echo("🎯 Triggers:")
|
||||
for trigger, enabled in mock_config['triggers'].items():
|
||||
status = "✅" if enabled else "❌"
|
||||
click.echo(f" {status} {trigger}")
|
||||
click.echo("")
|
||||
click.echo("⏱️ Polling:")
|
||||
click.echo(f" Enabled: {mock_config['polling']['enabled']}")
|
||||
click.echo(f" Interval: {mock_config['polling']['interval_seconds']}s")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from bridge service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.bridge_url}/config",
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
service_config = response.json()
|
||||
|
||||
click.echo("⚙️ Blockchain Event Bridge Configuration:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"🔗 Blockchain RPC URL: {service_config.get('blockchain_rpc_url', 'unknown')}")
|
||||
click.echo(f"💬 Gossip Backend: {service_config.get('gossip_backend', 'unknown')}")
|
||||
if service_config.get('gossip_broadcast_url'):
|
||||
click.echo(f"📡 Gossip Broadcast URL: {service_config['gossip_broadcast_url']}")
|
||||
click.echo(f"🎯 Coordinator API URL: {service_config.get('coordinator_api_url', 'unknown')}")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get config: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting config: {str(e)}", err=True)
|
||||
|
||||
@bridge.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def restart(test_mode):
|
||||
"""Restart blockchain event bridge service (via systemd)"""
|
||||
try:
|
||||
if test_mode:
|
||||
click.echo("🔄 Blockchain event bridge restart triggered (test mode)")
|
||||
click.echo("✅ Restart completed successfully")
|
||||
return
|
||||
|
||||
# Restart via systemd
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["sudo", "systemctl", "restart", "aitbc-blockchain-event-bridge"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
click.echo("🔄 Blockchain event bridge restart triggered")
|
||||
click.echo("✅ Restart completed successfully")
|
||||
else:
|
||||
click.echo(f"❌ Restart failed: {result.stderr}", err=True)
|
||||
except subprocess.TimeoutExpired:
|
||||
click.echo("❌ Restart timeout - service may be starting", err=True)
|
||||
except FileNotFoundError:
|
||||
click.echo("❌ systemctl not found - cannot restart service", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error restarting service: {str(e)}", err=True)
|
||||
|
||||
# Helper function to get config
|
||||
def get_config():
|
||||
"""Get CLI configuration"""
|
||||
try:
|
||||
from config import get_config
|
||||
return get_config()
|
||||
except ImportError:
|
||||
# Fallback for testing
|
||||
from types import SimpleNamespace
|
||||
return SimpleNamespace(
|
||||
bridge_url="http://localhost:8204",
|
||||
api_key="test-api-key"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
bridge()
|
||||
486
cli/commands/pool_hub.py
Normal file
486
cli/commands/pool_hub.py
Normal file
@@ -0,0 +1,486 @@
|
||||
"""
|
||||
Pool Hub CLI Commands for AITBC
|
||||
Commands for SLA monitoring, capacity planning, and billing integration
|
||||
"""
|
||||
|
||||
import click
|
||||
import json
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
@click.group()
|
||||
def pool_hub():
|
||||
"""Pool hub management commands for SLA monitoring and billing"""
|
||||
pass
|
||||
|
||||
@pool_hub.command()
|
||||
@click.argument('miner_id', required=False)
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def sla_metrics(miner_id, test_mode):
|
||||
"""Get SLA metrics for a miner or all miners"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
if miner_id:
|
||||
mock_metrics = {
|
||||
"miner_id": miner_id,
|
||||
"uptime_percentage": 97.5,
|
||||
"response_time_ms": 850,
|
||||
"job_completion_rate": 92.3,
|
||||
"capacity_availability": 85.0,
|
||||
"thresholds": {
|
||||
"uptime": 95.0,
|
||||
"response_time": 1000,
|
||||
"completion_rate": 90.0,
|
||||
"capacity": 80.0
|
||||
},
|
||||
"violations": [
|
||||
{
|
||||
"type": "response_time",
|
||||
"threshold": 1000,
|
||||
"actual": 1200,
|
||||
"timestamp": "2024-03-15T14:30:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
click.echo(f"📊 SLA Metrics for {miner_id}:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"⏱️ Uptime: {mock_metrics['uptime_percentage']}% (threshold: {mock_metrics['thresholds']['uptime']}%)")
|
||||
click.echo(f"⚡ Response Time: {mock_metrics['response_time_ms']}ms (threshold: {mock_metrics['thresholds']['response_time']}ms)")
|
||||
click.echo(f"✅ Job Completion Rate: {mock_metrics['job_completion_rate']}% (threshold: {mock_metrics['thresholds']['completion_rate']}%)")
|
||||
click.echo(f"📦 Capacity Availability: {mock_metrics['capacity_availability']}% (threshold: {mock_metrics['thresholds']['capacity']}%)")
|
||||
|
||||
if mock_metrics['violations']:
|
||||
click.echo("")
|
||||
click.echo("⚠️ Violations:")
|
||||
for v in mock_metrics['violations']:
|
||||
click.echo(f" {v['type']}: {v['actual']} vs threshold {v['threshold']} at {v['timestamp']}")
|
||||
else:
|
||||
mock_metrics = {
|
||||
"total_miners": 45,
|
||||
"average_uptime": 96.2,
|
||||
"average_response_time": 780,
|
||||
"average_completion_rate": 94.1,
|
||||
"average_capacity": 88.5,
|
||||
"miners_below_threshold": 3
|
||||
}
|
||||
|
||||
click.echo("📊 SLA Metrics (All Miners):")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"👥 Total Miners: {mock_metrics['total_miners']}")
|
||||
click.echo(f"⏱️ Average Uptime: {mock_metrics['average_uptime']}%")
|
||||
click.echo(f"⚡ Average Response Time: {mock_metrics['average_response_time']}ms")
|
||||
click.echo(f"✅ Average Completion Rate: {mock_metrics['average_completion_rate']}%")
|
||||
click.echo(f"📦 Average Capacity: {mock_metrics['average_capacity']}%")
|
||||
click.echo(f"⚠️ Miners Below Threshold: {mock_metrics['miners_below_threshold']}")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from pool-hub service
|
||||
config = get_config()
|
||||
|
||||
if miner_id:
|
||||
response = requests.get(
|
||||
f"{config.pool_hub_url}/sla/metrics/{miner_id}",
|
||||
timeout=30
|
||||
)
|
||||
else:
|
||||
response = requests.get(
|
||||
f"{config.pool_hub_url}/sla/metrics",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
metrics = response.json()
|
||||
|
||||
if miner_id:
|
||||
click.echo(f"📊 SLA Metrics for {miner_id}:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"⏱️ Uptime: {metrics.get('uptime_percentage', 0)}%")
|
||||
click.echo(f"⚡ Response Time: {metrics.get('response_time_ms', 0)}ms")
|
||||
click.echo(f"✅ Job Completion Rate: {metrics.get('job_completion_rate', 0)}%")
|
||||
click.echo(f"📦 Capacity Availability: {metrics.get('capacity_availability', 0)}%")
|
||||
else:
|
||||
click.echo("📊 SLA Metrics (All Miners):")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"👥 Total Miners: {metrics.get('total_miners', 0)}")
|
||||
click.echo(f"⏱️ Average Uptime: {metrics.get('average_uptime', 0)}%")
|
||||
click.echo(f"⚡ Average Response Time: {metrics.get('average_response_time', 0)}ms")
|
||||
click.echo(f"✅ Average Completion Rate: {metrics.get('average_completion_rate', 0)}%")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get SLA metrics: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting SLA metrics: {str(e)}", err=True)
|
||||
|
||||
@pool_hub.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def sla_violations(test_mode):
|
||||
"""Get SLA violations across all miners"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_violations = [
|
||||
{
|
||||
"miner_id": "miner_001",
|
||||
"type": "response_time",
|
||||
"threshold": 1000,
|
||||
"actual": 1200,
|
||||
"timestamp": "2024-03-15T14:30:00Z"
|
||||
},
|
||||
{
|
||||
"miner_id": "miner_002",
|
||||
"type": "uptime",
|
||||
"threshold": 95.0,
|
||||
"actual": 92.5,
|
||||
"timestamp": "2024-03-15T13:45:00Z"
|
||||
}
|
||||
]
|
||||
|
||||
click.echo("⚠️ SLA Violations:")
|
||||
click.echo("=" * 50)
|
||||
for v in mock_violations:
|
||||
click.echo(f"👤 Miner: {v['miner_id']}")
|
||||
click.echo(f" Type: {v['type']}")
|
||||
click.echo(f" Threshold: {v['threshold']}")
|
||||
click.echo(f" Actual: {v['actual']}")
|
||||
click.echo(f" Timestamp: {v['timestamp']}")
|
||||
click.echo("")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from pool-hub service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.pool_hub_url}/sla/violations",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
violations = response.json()
|
||||
|
||||
click.echo("⚠️ SLA Violations:")
|
||||
click.echo("=" * 50)
|
||||
for v in violations:
|
||||
click.echo(f"👤 Miner: {v['miner_id']}")
|
||||
click.echo(f" Type: {v['type']}")
|
||||
click.echo(f" Threshold: {v['threshold']}")
|
||||
click.echo(f" Actual: {v['actual']}")
|
||||
click.echo(f" Timestamp: {v['timestamp']}")
|
||||
click.echo("")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get violations: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting violations: {str(e)}", err=True)
|
||||
|
||||
@pool_hub.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def capacity_snapshots(test_mode):
|
||||
"""Get capacity planning snapshots"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_snapshots = [
|
||||
{
|
||||
"timestamp": "2024-03-15T00:00:00Z",
|
||||
"total_capacity": 1250,
|
||||
"available_capacity": 320,
|
||||
"utilization": 74.4,
|
||||
"active_miners": 42
|
||||
},
|
||||
{
|
||||
"timestamp": "2024-03-14T00:00:00Z",
|
||||
"total_capacity": 1200,
|
||||
"available_capacity": 350,
|
||||
"utilization": 70.8,
|
||||
"active_miners": 40
|
||||
}
|
||||
]
|
||||
|
||||
click.echo("📊 Capacity Snapshots:")
|
||||
click.echo("=" * 50)
|
||||
for s in mock_snapshots:
|
||||
click.echo(f"🕐 Timestamp: {s['timestamp']}")
|
||||
click.echo(f" Total Capacity: {s['total_capacity']} GPU")
|
||||
click.echo(f" Available: {s['available_capacity']} GPU")
|
||||
click.echo(f" Utilization: {s['utilization']}%")
|
||||
click.echo(f" Active Miners: {s['active_miners']}")
|
||||
click.echo("")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from pool-hub service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.pool_hub_url}/sla/capacity/snapshots",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
snapshots = response.json()
|
||||
|
||||
click.echo("📊 Capacity Snapshots:")
|
||||
click.echo("=" * 50)
|
||||
for s in snapshots:
|
||||
click.echo(f"🕐 Timestamp: {s['timestamp']}")
|
||||
click.echo(f" Total Capacity: {s['total_capacity']} GPU")
|
||||
click.echo(f" Available: {s['available_capacity']} GPU")
|
||||
click.echo(f" Utilization: {s['utilization']}%")
|
||||
click.echo(f" Active Miners: {s['active_miners']}")
|
||||
click.echo("")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get snapshots: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting snapshots: {str(e)}", err=True)
|
||||
|
||||
@pool_hub.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def capacity_forecast(test_mode):
|
||||
"""Get capacity forecast"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_forecast = {
|
||||
"forecast_days": 7,
|
||||
"current_capacity": 1250,
|
||||
"projected_capacity": 1400,
|
||||
"growth_rate": 12.0,
|
||||
"daily_projections": [
|
||||
{"day": 1, "capacity": 1280},
|
||||
{"day": 2, "capacity": 1310},
|
||||
{"day": 3, "capacity": 1340},
|
||||
{"day": 7, "capacity": 1400}
|
||||
]
|
||||
}
|
||||
|
||||
click.echo("🔮 Capacity Forecast:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"📅 Forecast Period: {mock_forecast['forecast_days']} days")
|
||||
click.echo(f"📊 Current Capacity: {mock_forecast['current_capacity']} GPU")
|
||||
click.echo(f"📈 Projected Capacity: {mock_forecast['projected_capacity']} GPU")
|
||||
click.echo(f"📊 Growth Rate: {mock_forecast['growth_rate']}%")
|
||||
click.echo("")
|
||||
click.echo("Daily Projections:")
|
||||
for p in mock_forecast['daily_projections']:
|
||||
click.echo(f" Day {p['day']}: {p['capacity']} GPU")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from pool-hub service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.pool_hub_url}/sla/capacity/forecast",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
forecast = response.json()
|
||||
|
||||
click.echo("🔮 Capacity Forecast:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"📅 Forecast Period: {forecast['forecast_days']} days")
|
||||
click.echo(f"📊 Current Capacity: {forecast['current_capacity']} GPU")
|
||||
click.echo(f"📈 Projected Capacity: {forecast['projected_capacity']} GPU")
|
||||
click.echo(f"📊 Growth Rate: {forecast['growth_rate']}%")
|
||||
click.echo("")
|
||||
click.echo("Daily Projections:")
|
||||
for p in forecast['daily_projections']:
|
||||
click.echo(f" Day {p['day']}: {p['capacity']} GPU")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get forecast: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting forecast: {str(e)}", err=True)
|
||||
|
||||
@pool_hub.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def capacity_recommendations(test_mode):
|
||||
"""Get scaling recommendations"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_recommendations = [
|
||||
{
|
||||
"type": "scale_up",
|
||||
"reason": "High utilization (>80%)",
|
||||
"action": "Add 50 GPU capacity",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"type": "optimize",
|
||||
"reason": "Imbalanced workload distribution",
|
||||
"action": "Rebalance miners across regions",
|
||||
"priority": "medium"
|
||||
}
|
||||
]
|
||||
|
||||
click.echo("💡 Capacity Recommendations:")
|
||||
click.echo("=" * 50)
|
||||
for r in mock_recommendations:
|
||||
click.echo(f"📌 Type: {r['type']}")
|
||||
click.echo(f" Reason: {r['reason']}")
|
||||
click.echo(f" Action: {r['action']}")
|
||||
click.echo(f" Priority: {r['priority']}")
|
||||
click.echo("")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from pool-hub service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.pool_hub_url}/sla/capacity/recommendations",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
recommendations = response.json()
|
||||
|
||||
click.echo("💡 Capacity Recommendations:")
|
||||
click.echo("=" * 50)
|
||||
for r in recommendations:
|
||||
click.echo(f"📌 Type: {r['type']}")
|
||||
click.echo(f" Reason: {r['reason']}")
|
||||
click.echo(f" Action: {r['action']}")
|
||||
click.echo(f" Priority: {r['priority']}")
|
||||
click.echo("")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get recommendations: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting recommendations: {str(e)}", err=True)
|
||||
|
||||
@pool_hub.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def billing_usage(test_mode):
|
||||
"""Get billing usage data"""
|
||||
try:
|
||||
if test_mode:
|
||||
# Mock data for testing
|
||||
mock_usage = {
|
||||
"period_start": "2024-03-01T00:00:00Z",
|
||||
"period_end": "2024-03-31T23:59:59Z",
|
||||
"total_gpu_hours": 45678,
|
||||
"total_api_calls": 1234567,
|
||||
"total_compute_hours": 23456,
|
||||
"total_cost": 12500.50,
|
||||
"by_miner": [
|
||||
{"miner_id": "miner_001", "gpu_hours": 12000, "cost": 3280.50},
|
||||
{"miner_id": "miner_002", "gpu_hours": 8900, "cost": 2435.00}
|
||||
]
|
||||
}
|
||||
|
||||
click.echo("💰 Billing Usage:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"📅 Period: {mock_usage['period_start']} to {mock_usage['period_end']}")
|
||||
click.echo(f"⚡ Total GPU Hours: {mock_usage['total_gpu_hours']}")
|
||||
click.echo(f"📞 Total API Calls: {mock_usage['total_api_calls']}")
|
||||
click.echo(f"🖥️ Total Compute Hours: {mock_usage['total_compute_hours']}")
|
||||
click.echo(f"💵 Total Cost: ${mock_usage['total_cost']:.2f}")
|
||||
click.echo("")
|
||||
click.echo("By Miner:")
|
||||
for m in mock_usage['by_miner']:
|
||||
click.echo(f" {m['miner_id']}: {m['gpu_hours']} GPUh, ${m['cost']:.2f}")
|
||||
|
||||
return
|
||||
|
||||
# Fetch from pool-hub service
|
||||
config = get_config()
|
||||
response = requests.get(
|
||||
f"{config.pool_hub_url}/sla/billing/usage",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
usage = response.json()
|
||||
|
||||
click.echo("💰 Billing Usage:")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"📅 Period: {usage['period_start']} to {usage['period_end']}")
|
||||
click.echo(f"⚡ Total GPU Hours: {usage['total_gpu_hours']}")
|
||||
click.echo(f"📞 Total API Calls: {usage['total_api_calls']}")
|
||||
click.echo(f"🖥️ Total Compute Hours: {usage['total_compute_hours']}")
|
||||
click.echo(f"💵 Total Cost: ${usage['total_cost']:.2f}")
|
||||
click.echo("")
|
||||
click.echo("By Miner:")
|
||||
for m in usage['by_miner']:
|
||||
click.echo(f" {m['miner_id']}: {m['gpu_hours']} GPUh, ${m['cost']:.2f}")
|
||||
else:
|
||||
click.echo(f"❌ Failed to get billing usage: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error getting billing usage: {str(e)}", err=True)
|
||||
|
||||
@pool_hub.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def billing_sync(test_mode):
|
||||
"""Trigger billing sync with coordinator-api"""
|
||||
try:
|
||||
if test_mode:
|
||||
click.echo("🔄 Billing sync triggered (test mode)")
|
||||
click.echo("✅ Sync completed successfully")
|
||||
return
|
||||
|
||||
# Trigger sync with pool-hub service
|
||||
config = get_config()
|
||||
response = requests.post(
|
||||
f"{config.pool_hub_url}/sla/billing/sync",
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
click.echo("🔄 Billing sync triggered")
|
||||
click.echo(f"✅ Sync completed: {result.get('message', 'Success')}")
|
||||
else:
|
||||
click.echo(f"❌ Billing sync failed: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error triggering billing sync: {str(e)}", err=True)
|
||||
|
||||
@pool_hub.command()
|
||||
@click.option('--test-mode', is_flag=True, help='Run in test mode')
|
||||
def collect_metrics(test_mode):
|
||||
"""Trigger SLA metrics collection"""
|
||||
try:
|
||||
if test_mode:
|
||||
click.echo("📊 SLA metrics collection triggered (test mode)")
|
||||
click.echo("✅ Collection completed successfully")
|
||||
return
|
||||
|
||||
# Trigger collection with pool-hub service
|
||||
config = get_config()
|
||||
response = requests.post(
|
||||
f"{config.pool_hub_url}/sla/metrics/collect",
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
click.echo("📊 SLA metrics collection triggered")
|
||||
click.echo(f"✅ Collection completed: {result.get('message', 'Success')}")
|
||||
else:
|
||||
click.echo(f"❌ Metrics collection failed: {response.text}", err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ Error triggering metrics collection: {str(e)}", err=True)
|
||||
|
||||
# Helper function to get config
|
||||
def get_config():
|
||||
"""Get CLI configuration"""
|
||||
try:
|
||||
from config import get_config
|
||||
return get_config()
|
||||
except ImportError:
|
||||
# Fallback for testing
|
||||
from types import SimpleNamespace
|
||||
return SimpleNamespace(
|
||||
pool_hub_url="http://localhost:8012",
|
||||
api_key="test-api-key"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
pool_hub()
|
||||
Reference in New Issue
Block a user