refactor: rename packages to shorter names
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Integration Tests / test-service-integration (push) Failing after 3s
Python Tests / test-python (push) Failing after 32s
Security Scanning / security-scan (push) Successful in 32s
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Integration Tests / test-service-integration (push) Failing after 3s
Python Tests / test-python (push) Failing after 32s
Security Scanning / security-scan (push) Successful in 32s
- Renamed aitbc-ai-service to aitbc-ai - Renamed aitbc-edge-api to aitbc-edge - Updated pyproject.toml files with new package names - Renamed directories and package modules - Updated references in documentation and scripts - Updated systemd service references
This commit is contained in:
153
apps/aitbc-edge/README.md
Normal file
153
apps/aitbc-edge/README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Edge API Service
|
||||
|
||||
REST API for AITBC island and edge operations, providing HTTP equivalents for CLI commands.
|
||||
|
||||
## Overview
|
||||
|
||||
The Edge API Service exposes island and edge operations via REST API, following the coordinator-api pattern. It integrates with the existing GPU service and blockchain node RPC.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Port:** 8103
|
||||
- **Database:** PostgreSQL (aitbc_edge)
|
||||
- **Communication:**
|
||||
- Blockchain node RPC: localhost:8006
|
||||
- GPU service: localhost:8101
|
||||
- **Authentication:** JWT tokens (same as coordinator-api)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cd /opt/aitbc/apps/edge-api
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
## Database Setup
|
||||
|
||||
Create PostgreSQL database and user:
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql
|
||||
CREATE DATABASE aitbc_edge;
|
||||
CREATE USER aitbc_edge WITH PASSWORD 'password';
|
||||
GRANT ALL PRIVILEGES ON DATABASE aitbc_edge TO aitbc_edge;
|
||||
\q
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
cd /opt/aitbc/apps/edge-api
|
||||
python -m edge_api.main
|
||||
```
|
||||
|
||||
### Production (systemd)
|
||||
|
||||
```bash
|
||||
# Install service
|
||||
sudo cp edge-api.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable edge-api
|
||||
sudo systemctl start edge-api
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Health Checks
|
||||
|
||||
- `GET /health` - Health check
|
||||
- `GET /ready` - Readiness check
|
||||
|
||||
### Island Operations (Phase 2)
|
||||
|
||||
- `POST /v1/islands/join` - Join an island
|
||||
- `POST /v1/islands/leave` - Leave an island
|
||||
- `GET /v1/islands` - List all islands
|
||||
- `GET /v1/islands/{island_id}` - Get island details
|
||||
- `POST /v1/islands/bridge` - Request bridge
|
||||
|
||||
### GPU Operations (Phase 3)
|
||||
|
||||
- `POST /v1/gpu/listings` - List GPU on island
|
||||
- `GET /v1/gpu/listings` - Get GPU listings
|
||||
- `DELETE /v1/gpu/listings/{listing_id}` - Remove GPU listing
|
||||
- `GET /v1/gpu/scan` - Scan GPUs
|
||||
|
||||
### Edge Database Operations (Phase 4)
|
||||
|
||||
- `POST /v1/database/init` - Initialize edge database
|
||||
- `GET /v1/database` - Get edge database status
|
||||
- `DELETE /v1/database` - Delete edge database
|
||||
- `POST /v1/database/sync` - Sync edge database
|
||||
|
||||
### Edge Serve Operations (Phase 5)
|
||||
|
||||
- `POST /v1/serve/start` - Start serving
|
||||
- `POST /v1/serve/stop` - Stop serving
|
||||
- `GET /v1/serve/status` - Get serve status
|
||||
- `GET /v1/serve/requests` - Get pending requests
|
||||
- `POST /v1/serve/requests/{request_id}/complete` - Complete request
|
||||
|
||||
### Edge Metrics (Phase 6)
|
||||
|
||||
- `GET /v1/metrics` - Get edge metrics
|
||||
- `GET /v1/metrics/gpu` - Get GPU metrics
|
||||
- `GET /v1/metrics/database` - Get database metrics
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**Phase 1: Foundation** ✅
|
||||
- Service structure
|
||||
- FastAPI application
|
||||
- Database configuration
|
||||
- Basic stub endpoints
|
||||
|
||||
**Phase 2: Island Operations** ⏳
|
||||
- Island join/leave endpoints
|
||||
- Blockchain RPC integration
|
||||
- Island listing
|
||||
|
||||
**Phase 3: GPU Operations** ⏳
|
||||
- GPU listing endpoints
|
||||
- GPU service integration
|
||||
- GPU metrics
|
||||
|
||||
**Phase 4: Edge Database** ⏳
|
||||
- Database initialization
|
||||
- Database sync
|
||||
- Database management
|
||||
|
||||
**Phase 5: Edge Serve** ⏳
|
||||
- Serve start/stop
|
||||
- Request queue
|
||||
- Request processing
|
||||
|
||||
**Phase 6: Metrics** ⏳
|
||||
- Metrics collection
|
||||
- Metrics endpoints
|
||||
- Performance monitoring
|
||||
|
||||
## CLI Integration
|
||||
|
||||
CLI commands will be updated to use REST API instead of placeholder implementations:
|
||||
|
||||
- `aitbc island join` → `POST /v1/islands/join`
|
||||
- `aitbc gpu list-island` → `POST /v1/gpu/listings`
|
||||
- `aitbc database init-edge` → `POST /v1/database/init`
|
||||
- `aitbc edge serve` → `POST /v1/serve/start`
|
||||
- `aitbc edge metrics` → `GET /v1/metrics`
|
||||
|
||||
## Agent SDK Integration
|
||||
|
||||
Agent SDK will be updated to use REST API for island and edge operations.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- FastAPI
|
||||
- SQLAlchemy + SQLModel
|
||||
- PostgreSQL
|
||||
- httpx
|
||||
- PyJWT
|
||||
- uvicorn
|
||||
22
apps/aitbc-edge/edge-api.service
Normal file
22
apps/aitbc-edge/edge-api.service
Normal file
@@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=AITBC Edge API Service
|
||||
After=network.target postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/aitbc/apps/edge-api
|
||||
Environment="PATH=/opt/aitbc/venv/bin"
|
||||
Environment="PYTHONPATH=/opt/aitbc/packages/py/aitbc-core/src:/opt/aitbc/apps/edge-api/src:/opt/aitbc"
|
||||
Environment="DATABASE_URL=postgresql+asyncpg://aitbc_edge:password@localhost:5432/aitbc_edge"
|
||||
Environment="BLOCKCHAIN_RPC_HOST=localhost"
|
||||
Environment="BLOCKCHAIN_RPC_PORT=8006"
|
||||
Environment="GPU_SERVICE_HOST=localhost"
|
||||
Environment="GPU_SERVICE_PORT=8101"
|
||||
Environment="JWT_SECRET_KEY=your-secret-key-change-in-production"
|
||||
Environment="API_PORT=8103"
|
||||
ExecStart=/opt/aitbc/venv/bin/python -m edge_api.main
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
24
apps/aitbc-edge/pyproject.toml
Normal file
24
apps/aitbc-edge/pyproject.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[project]
|
||||
name = "aitbc-edge"
|
||||
version = "0.1.0"
|
||||
description = "Edge API Service for AITBC island and edge operations"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"fastapi>=0.115.6",
|
||||
"uvicorn>=0.34.0",
|
||||
"sqlmodel>=0.0.38",
|
||||
"sqlalchemy[asyncio]>=2.0.49",
|
||||
"asyncpg>=0.30.0",
|
||||
"httpx>=0.28.1",
|
||||
"pydantic>=2.11.0",
|
||||
"pydantic-settings>=2.13.1",
|
||||
"python-jose[cryptography]>=3.3.0",
|
||||
"passlib[bcrypt]>=1.7.4",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/aitbc_edge"]
|
||||
3
apps/aitbc-edge/src/aitbc_edge/__init__.py
Normal file
3
apps/aitbc-edge/src/aitbc_edge/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""Edge API Service for AITBC island and edge operations"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
9
apps/aitbc-edge/src/aitbc_edge/clients/__init__.py
Normal file
9
apps/aitbc-edge/src/aitbc_edge/clients/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""Clients for Edge API Service"""
|
||||
|
||||
from .blockchain_rpc import BlockchainRPCClient
|
||||
from .gpu_service import GPUServiceClient
|
||||
|
||||
__all__ = [
|
||||
"BlockchainRPCClient",
|
||||
"GPUServiceClient",
|
||||
]
|
||||
65
apps/aitbc-edge/src/aitbc_edge/clients/blockchain_rpc.py
Normal file
65
apps/aitbc-edge/src/aitbc_edge/clients/blockchain_rpc.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""Blockchain RPC client for Edge API Service"""
|
||||
|
||||
import httpx
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
from ..config import settings
|
||||
|
||||
|
||||
class BlockchainRPCClient:
|
||||
"""Client for blockchain node RPC communication"""
|
||||
|
||||
def __init__(self):
|
||||
self.base_url = f"http://{settings.blockchain_rpc_host}:{settings.blockchain_rpc_port}"
|
||||
self.client = httpx.AsyncClient(timeout=30.0)
|
||||
|
||||
async def close(self):
|
||||
"""Close the HTTP client"""
|
||||
await self.client.aclose()
|
||||
|
||||
async def join_island(self, island_id: str, island_name: str, chain_id: str, role: str = "compute-provider", is_hub: bool = False) -> Dict[str, Any]:
|
||||
"""Join island via blockchain RPC"""
|
||||
response = await self.client.post(
|
||||
f"{self.base_url}/rpc/islands/join",
|
||||
json={
|
||||
"island_id": island_id,
|
||||
"island_name": island_name,
|
||||
"chain_id": chain_id,
|
||||
"role": role,
|
||||
"is_hub": is_hub
|
||||
}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def leave_island(self, island_id: str) -> Dict[str, Any]:
|
||||
"""Leave island via blockchain RPC"""
|
||||
response = await self.client.post(
|
||||
f"{self.base_url}/rpc/islands/leave",
|
||||
json={"island_id": island_id}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_island_info(self, island_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get island info via blockchain RPC"""
|
||||
response = await self.client.get(f"{self.base_url}/rpc/islands/{island_id}")
|
||||
if response.status_code == 404:
|
||||
return None
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def list_islands(self) -> Dict[str, Any]:
|
||||
"""List all islands via blockchain RPC"""
|
||||
response = await self.client.get(f"{self.base_url}/rpc/islands")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def request_bridge(self, target_island_id: str) -> Dict[str, Any]:
|
||||
"""Request bridge via blockchain RPC"""
|
||||
response = await self.client.post(
|
||||
f"{self.base_url}/rpc/islands/bridge",
|
||||
json={"target_island_id": target_island_id}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
50
apps/aitbc-edge/src/aitbc_edge/clients/gpu_service.py
Normal file
50
apps/aitbc-edge/src/aitbc_edge/clients/gpu_service.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""GPU service client for Edge API Service"""
|
||||
|
||||
import httpx
|
||||
from typing import Dict, Optional, Any, List
|
||||
|
||||
from ..config import settings
|
||||
|
||||
|
||||
class GPUServiceClient:
|
||||
"""Client for GPU service communication"""
|
||||
|
||||
def __init__(self):
|
||||
self.base_url = f"http://{settings.gpu_service_host}:{settings.gpu_service_port}"
|
||||
self.client = httpx.AsyncClient(timeout=30.0)
|
||||
|
||||
async def close(self):
|
||||
"""Close the HTTP client"""
|
||||
await self.client.aclose()
|
||||
|
||||
async def scan_gpus(self, miner_id: str) -> Dict[str, Any]:
|
||||
"""Scan GPUs via GPU service"""
|
||||
response = await self.client.post(f"{self.base_url}/v1/marketplace/edge-gpu/scan/{miner_id}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_gpu_profiles(self, architecture: str = None, edge_optimized: bool = None, min_memory_gb: int = None) -> List[Dict[str, Any]]:
|
||||
"""Get GPU profiles via GPU service"""
|
||||
params = {}
|
||||
if architecture:
|
||||
params["architecture"] = architecture
|
||||
if edge_optimized is not None:
|
||||
params["edge_optimized"] = edge_optimized
|
||||
if min_memory_gb is not None:
|
||||
params["min_memory_gb"] = min_memory_gb
|
||||
|
||||
response = await self.client.get(f"{self.base_url}/v1/marketplace/edge-gpu/profiles", params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_gpu_metrics(self, gpu_id: str, limit: int = 100) -> List[Dict[str, Any]]:
|
||||
"""Get GPU metrics via GPU service"""
|
||||
response = await self.client.get(f"{self.base_url}/v1/marketplace/edge-gpu/metrics/{gpu_id}", params={"limit": limit})
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_miner_gpus(self, miner_id: str) -> List[Dict[str, Any]]:
|
||||
"""Get GPUs registered by a miner"""
|
||||
response = await self.client.get(f"{self.base_url}/v1/miners/{miner_id}/gpus")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
43
apps/aitbc-edge/src/aitbc_edge/config.py
Normal file
43
apps/aitbc-edge/src/aitbc_edge/config.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Configuration for Edge API Service"""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Edge API settings"""
|
||||
|
||||
# API settings
|
||||
api_host: str = "0.0.0.0"
|
||||
api_port: int = 8103
|
||||
api_prefix: str = "/v1"
|
||||
|
||||
# Database settings
|
||||
database_url: str = "postgresql+asyncpg://aitbc_edge:password@localhost:5432/aitbc_edge"
|
||||
|
||||
# Blockchain node RPC settings
|
||||
blockchain_rpc_host: str = "localhost"
|
||||
blockchain_rpc_port: int = 8006
|
||||
|
||||
# GPU service settings
|
||||
gpu_service_host: str = "localhost"
|
||||
gpu_service_port: int = 8101
|
||||
|
||||
# JWT settings
|
||||
jwt_secret_key: str = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production")
|
||||
jwt_algorithm: str = "HS256"
|
||||
jwt_expiration_hours: int = 24
|
||||
|
||||
# CORS settings
|
||||
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:8080"]
|
||||
|
||||
# Logging
|
||||
log_level: str = "INFO"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = False
|
||||
|
||||
|
||||
settings = Settings()
|
||||
104
apps/aitbc-edge/src/aitbc_edge/main.py
Normal file
104
apps/aitbc-edge/src/aitbc_edge/main.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""Main FastAPI application for Edge API Service"""
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from aitbc import get_logger
|
||||
|
||||
from .config import settings
|
||||
from .storage import init_db
|
||||
from .routers import islands_router as islands, gpu_router as gpu, database_router as database, serve_router as serve, metrics_router as metrics
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Lifespan context manager for startup/shutdown"""
|
||||
# Startup
|
||||
logger.info("Starting Edge API Service")
|
||||
await init_db()
|
||||
logger.info("Database initialized")
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown
|
||||
logger.info("Shutting down Edge API Service")
|
||||
|
||||
|
||||
# Create FastAPI application
|
||||
app = FastAPI(
|
||||
title="Edge API Service",
|
||||
description="REST API for AITBC island and edge operations",
|
||||
version="0.1.0",
|
||||
lifespan=lifespan,
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
)
|
||||
|
||||
# CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.cors_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
# Health check endpoint
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "edge-api",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
|
||||
|
||||
# Readiness check endpoint
|
||||
@app.get("/ready")
|
||||
async def readiness_check():
|
||||
"""Readiness check endpoint"""
|
||||
# TODO: Check database connection, blockchain RPC, GPU service
|
||||
return {
|
||||
"status": "ready",
|
||||
"service": "edge-api",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
|
||||
|
||||
# Include routers
|
||||
app.include_router(islands, prefix=f"{settings.api_prefix}/islands", tags=["islands"])
|
||||
app.include_router(gpu, prefix=f"{settings.api_prefix}/gpu", tags=["gpu"])
|
||||
app.include_router(database, prefix=f"{settings.api_prefix}/database", tags=["database"])
|
||||
app.include_router(serve, prefix=f"{settings.api_prefix}/serve", tags=["serve"])
|
||||
app.include_router(metrics, prefix=f"{settings.api_prefix}/metrics", tags=["metrics"])
|
||||
|
||||
|
||||
# Global exception handler
|
||||
@app.exception_handler(Exception)
|
||||
async def global_exception_handler(request: Request, exc: Exception):
|
||||
"""Global exception handler"""
|
||||
logger.error(f"Unhandled exception: {exc}", exc_info=True)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"error": "Internal server error",
|
||||
"detail": str(exc)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
"edge_api.main:app",
|
||||
host=settings.api_host,
|
||||
port=settings.api_port,
|
||||
reload=True,
|
||||
log_level=settings.log_level.lower()
|
||||
)
|
||||
15
apps/aitbc-edge/src/aitbc_edge/routers/__init__.py
Normal file
15
apps/aitbc-edge/src/aitbc_edge/routers/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Routers for Edge API Service"""
|
||||
|
||||
from .islands import router as islands_router
|
||||
from .gpu import router as gpu_router
|
||||
from .database import router as database_router
|
||||
from .serve import router as serve_router
|
||||
from .metrics import router as metrics_router
|
||||
|
||||
__all__ = [
|
||||
"islands_router",
|
||||
"gpu_router",
|
||||
"database_router",
|
||||
"serve_router",
|
||||
"metrics_router",
|
||||
]
|
||||
60
apps/aitbc-edge/src/aitbc_edge/routers/database.py
Normal file
60
apps/aitbc-edge/src/aitbc_edge/routers/database.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Database operations router for Edge API Service"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..services.database_service import DatabaseService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class InitDatabaseRequest(BaseModel):
|
||||
"""Request model for initializing a database"""
|
||||
database_id: str
|
||||
island_id: str
|
||||
capacity_gb: int
|
||||
|
||||
|
||||
def get_database_service() -> DatabaseService:
|
||||
"""Dependency injection for database service"""
|
||||
return DatabaseService()
|
||||
|
||||
|
||||
@router.post("/init")
|
||||
async def init_database(request: InitDatabaseRequest, svc: DatabaseService = Depends(get_database_service)):
|
||||
"""Initialize edge database"""
|
||||
result = await svc.init_database(request.database_id, request.island_id, request.capacity_gb)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_databases(island_id: str = Query(None), svc: DatabaseService = Depends(get_database_service)):
|
||||
"""List databases, optionally filtered by island_id"""
|
||||
databases = await svc.list_databases(island_id)
|
||||
return {"databases": databases, "total": len(databases)}
|
||||
|
||||
|
||||
@router.get("/{database_id}")
|
||||
async def get_database(database_id: str, svc: DatabaseService = Depends(get_database_service)):
|
||||
"""Get database details"""
|
||||
db = await svc.get_database(database_id)
|
||||
if db is None:
|
||||
raise HTTPException(status_code=404, detail=f"Database {database_id} not found")
|
||||
return db
|
||||
|
||||
|
||||
@router.delete("/{database_id}")
|
||||
async def delete_database(database_id: str, svc: DatabaseService = Depends(get_database_service)):
|
||||
"""Delete database"""
|
||||
success = await svc.delete_database(database_id)
|
||||
if success:
|
||||
return {"message": f"Database {database_id} deleted"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"Database {database_id} not found")
|
||||
|
||||
|
||||
@router.post("/{database_id}/sync")
|
||||
async def sync_database(database_id: str, svc: DatabaseService = Depends(get_database_service)):
|
||||
"""Sync database from source"""
|
||||
result = await svc.sync_database(database_id)
|
||||
return result
|
||||
67
apps/aitbc-edge/src/aitbc_edge/routers/gpu.py
Normal file
67
apps/aitbc-edge/src/aitbc_edge/routers/gpu.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""GPU operations router for Edge API Service"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..services.gpu_service import GPUService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class ScanGPUsRequest(BaseModel):
|
||||
"""Request model for scanning GPUs"""
|
||||
miner_id: str
|
||||
|
||||
|
||||
def get_gpu_service() -> GPUService:
|
||||
"""Dependency injection for GPU service"""
|
||||
return GPUService()
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_gpus(
|
||||
architecture: str = Query(None),
|
||||
edge_optimized: bool = Query(None),
|
||||
min_memory_gb: int = Query(None),
|
||||
svc: GPUService = Depends(get_gpu_service)
|
||||
):
|
||||
"""List all GPUs"""
|
||||
gpus = await svc.list_gpus(architecture=architecture, edge_optimized=edge_optimized, min_memory_gb=min_memory_gb)
|
||||
return {"gpus": gpus, "total": len(gpus)}
|
||||
|
||||
|
||||
@router.get("/{gpu_id}")
|
||||
async def get_gpu_listing(gpu_id: str, svc: GPUService = Depends(get_gpu_service)):
|
||||
"""Get GPU listing details"""
|
||||
gpu = await svc.get_gpu_listing(gpu_id)
|
||||
if gpu is None:
|
||||
raise HTTPException(status_code=404, detail=f"GPU {gpu_id} not found")
|
||||
return gpu
|
||||
|
||||
|
||||
@router.delete("/{gpu_id}")
|
||||
async def remove_gpu_listing(gpu_id: str, svc: GPUService = Depends(get_gpu_service)):
|
||||
"""Remove GPU listing"""
|
||||
success = await svc.remove_gpu_listing(gpu_id)
|
||||
if success:
|
||||
return {"message": f"GPU {gpu_id} removed"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"GPU {gpu_id} not found")
|
||||
|
||||
|
||||
@router.post("/scan")
|
||||
async def scan_gpus(request: ScanGPUsRequest, svc: GPUService = Depends(get_gpu_service)):
|
||||
"""Scan GPUs for a miner"""
|
||||
result = await svc.scan_gpus(request.miner_id)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/{gpu_id}/metrics")
|
||||
async def get_gpu_metrics(
|
||||
gpu_id: str,
|
||||
limit: int = Query(100),
|
||||
svc: GPUService = Depends(get_gpu_service)
|
||||
):
|
||||
"""Get GPU metrics"""
|
||||
metrics = await svc.get_gpu_metrics(gpu_id, limit)
|
||||
return {"gpu_id": gpu_id, "metrics": metrics, "total": len(metrics)}
|
||||
75
apps/aitbc-edge/src/aitbc_edge/routers/islands.py
Normal file
75
apps/aitbc-edge/src/aitbc_edge/routers/islands.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""Island operations router for Edge API Service"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..services.island_service import IslandService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class JoinIslandRequest(BaseModel):
|
||||
"""Request model for joining an island"""
|
||||
island_id: str
|
||||
island_name: str
|
||||
chain_id: str
|
||||
role: str = Field(default="compute-provider")
|
||||
is_hub: bool = Field(default=False)
|
||||
|
||||
|
||||
class LeaveIslandRequest(BaseModel):
|
||||
"""Request model for leaving an island"""
|
||||
island_id: str
|
||||
|
||||
|
||||
class BridgeRequestRequest(BaseModel):
|
||||
"""Request model for requesting a bridge"""
|
||||
target_island_id: str
|
||||
|
||||
|
||||
def get_island_service() -> IslandService:
|
||||
"""Dependency injection for island service"""
|
||||
return IslandService()
|
||||
|
||||
|
||||
@router.post("/join")
|
||||
async def join_island(request: JoinIslandRequest, svc: IslandService = Depends(get_island_service)):
|
||||
"""Join an island"""
|
||||
result = await svc.join_island(
|
||||
island_id=request.island_id,
|
||||
island_name=request.island_name,
|
||||
chain_id=request.chain_id,
|
||||
role=request.role,
|
||||
is_hub=request.is_hub
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/leave")
|
||||
async def leave_island(request: LeaveIslandRequest, svc: IslandService = Depends(get_island_service)):
|
||||
"""Leave an island"""
|
||||
result = await svc.leave_island(request.island_id)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_islands(svc: IslandService = Depends(get_island_service)):
|
||||
"""List all islands"""
|
||||
islands = await svc.list_islands()
|
||||
return {"islands": islands, "total": len(islands)}
|
||||
|
||||
|
||||
@router.get("/{island_id}")
|
||||
async def get_island(island_id: str, svc: IslandService = Depends(get_island_service)):
|
||||
"""Get island details"""
|
||||
island = await svc.get_island(island_id)
|
||||
if island is None:
|
||||
raise HTTPException(status_code=404, detail=f"Island {island_id} not found")
|
||||
return island
|
||||
|
||||
|
||||
@router.post("/bridge")
|
||||
async def request_bridge(request: BridgeRequestRequest, svc: IslandService = Depends(get_island_service)):
|
||||
"""Request bridge to another island"""
|
||||
result = await svc.request_bridge(request.target_island_id)
|
||||
return result
|
||||
52
apps/aitbc-edge/src/aitbc_edge/routers/metrics.py
Normal file
52
apps/aitbc-edge/src/aitbc_edge/routers/metrics.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Metrics operations router for Edge API Service"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..services.metrics_service import MetricsService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class RecordMetricsRequest(BaseModel):
|
||||
"""Request model for recording metrics"""
|
||||
gpu_id: str
|
||||
metrics: dict
|
||||
|
||||
|
||||
def get_metrics_service() -> MetricsService:
|
||||
"""Dependency injection for metrics service"""
|
||||
return MetricsService()
|
||||
|
||||
|
||||
@router.post("/")
|
||||
async def record_metrics(request: RecordMetricsRequest, svc: MetricsService = Depends(get_metrics_service)):
|
||||
"""Record edge metrics"""
|
||||
result = await svc.record_metrics(request.gpu_id, request.metrics)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_metrics(gpu_id: str = Query(None), limit: int = Query(100), svc: MetricsService = Depends(get_metrics_service)):
|
||||
"""List metrics, optionally filtered by gpu_id"""
|
||||
metrics = await svc.list_metrics(gpu_id, limit)
|
||||
return {"metrics": metrics, "total": len(metrics)}
|
||||
|
||||
|
||||
@router.get("/{metric_id}")
|
||||
async def get_metrics(metric_id: str, svc: MetricsService = Depends(get_metrics_service)):
|
||||
"""Get metric details"""
|
||||
metric = await svc.get_metrics(metric_id)
|
||||
if metric is None:
|
||||
raise HTTPException(status_code=404, detail=f"Metric {metric_id} not found")
|
||||
return metric
|
||||
|
||||
|
||||
@router.delete("/{metric_id}")
|
||||
async def delete_metrics(metric_id: str, svc: MetricsService = Depends(get_metrics_service)):
|
||||
"""Delete metric"""
|
||||
success = await svc.delete_metrics(metric_id)
|
||||
if success:
|
||||
return {"message": f"Metric {metric_id} deleted"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"Metric {metric_id} not found")
|
||||
63
apps/aitbc-edge/src/aitbc_edge/routers/serve.py
Normal file
63
apps/aitbc-edge/src/aitbc_edge/routers/serve.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Edge serve operations router for Edge API Service"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..services.serve_service import ServeService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class SubmitComputeRequest(BaseModel):
|
||||
"""Request model for submitting compute request"""
|
||||
gpu_id: str
|
||||
model_name: str
|
||||
input_data: dict
|
||||
priority: str = Field(default="normal")
|
||||
|
||||
|
||||
def get_serve_service() -> ServeService:
|
||||
"""Dependency injection for serve service"""
|
||||
return ServeService()
|
||||
|
||||
|
||||
@router.post("/requests")
|
||||
async def submit_compute_request(request: SubmitComputeRequest, svc: ServeService = Depends(get_serve_service)):
|
||||
"""Submit compute request"""
|
||||
result = await svc.submit_compute_request(request.gpu_id, request.model_name, request.input_data, request.priority)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/requests")
|
||||
async def list_compute_requests(gpu_id: str = Query(None), status: str = Query(None), svc: ServeService = Depends(get_serve_service)):
|
||||
"""List compute requests, optionally filtered"""
|
||||
requests = await svc.list_compute_requests(gpu_id, status)
|
||||
return {"requests": requests, "total": len(requests)}
|
||||
|
||||
|
||||
@router.get("/requests/{request_id}")
|
||||
async def get_compute_request(request_id: str, svc: ServeService = Depends(get_serve_service)):
|
||||
"""Get compute request details"""
|
||||
req = await svc.get_compute_request(request_id)
|
||||
if req is None:
|
||||
raise HTTPException(status_code=404, detail=f"Request {request_id} not found")
|
||||
return req
|
||||
|
||||
|
||||
@router.post("/requests/{request_id}/cancel")
|
||||
async def cancel_compute_request(request_id: str, svc: ServeService = Depends(get_serve_service)):
|
||||
"""Cancel compute request"""
|
||||
success = await svc.cancel_compute_request(request_id)
|
||||
if success:
|
||||
return {"message": f"Request {request_id} cancelled"}
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail=f"Request {request_id} cannot be cancelled")
|
||||
|
||||
|
||||
@router.get("/requests/{request_id}/result")
|
||||
async def get_compute_result(request_id: str, svc: ServeService = Depends(get_serve_service)):
|
||||
"""Get compute result"""
|
||||
result = await svc.get_compute_result(request_id)
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail=f"Result for request {request_id} not found")
|
||||
return result
|
||||
17
apps/aitbc-edge/src/aitbc_edge/schemas/__init__.py
Normal file
17
apps/aitbc-edge/src/aitbc_edge/schemas/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Schemas for Edge API Service"""
|
||||
|
||||
from .island import IslandMembership, BridgeRequest
|
||||
from .gpu import GPUListing
|
||||
from .database import EdgeDatabase
|
||||
from .serve import ComputeRequest, ComputeResult
|
||||
from .metrics import EdgeMetrics
|
||||
|
||||
__all__ = [
|
||||
"IslandMembership",
|
||||
"BridgeRequest",
|
||||
"GPUListing",
|
||||
"EdgeDatabase",
|
||||
"ComputeRequest",
|
||||
"ComputeResult",
|
||||
"EdgeMetrics",
|
||||
]
|
||||
31
apps/aitbc-edge/src/aitbc_edge/schemas/database.py
Normal file
31
apps/aitbc-edge/src/aitbc_edge/schemas/database.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""Edge database-related schemas for Edge API Service"""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import JSON, Column
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class EdgeDatabase(SQLModel, table=True):
|
||||
"""Edge database instance"""
|
||||
|
||||
__tablename__ = "edge_databases"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: str = Field(default_factory=lambda: f"edge_db_{uuid4().hex[:8]}", primary_key=True)
|
||||
database_id: str = Field(index=True)
|
||||
island_id: str = Field(index=True)
|
||||
capacity_gb: int
|
||||
used_gb: int = Field(default=0)
|
||||
status: str = Field(default="initialized", index=True) # initialized, active, syncing, error
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
# Sync information
|
||||
last_sync_at: datetime | None = Field(default=None)
|
||||
sync_status: str = Field(default="idle") # idle, syncing, error
|
||||
records_synced: int = Field(default=0)
|
||||
|
||||
# Database metadata
|
||||
extra_data: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=True))
|
||||
35
apps/aitbc-edge/src/aitbc_edge/schemas/gpu.py
Normal file
35
apps/aitbc-edge/src/aitbc_edge/schemas/gpu.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""GPU-related schemas for Edge API Service"""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import JSON, Column
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class GPUListing(SQLModel, table=True):
|
||||
"""GPU listing on island"""
|
||||
|
||||
__tablename__ = "gpu_listings"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: str = Field(default_factory=lambda: f"gpu_listing_{uuid4().hex[:8]}", primary_key=True)
|
||||
listing_id: str = Field(index=True)
|
||||
island_id: str = Field(index=True)
|
||||
miner_id: str = Field(index=True)
|
||||
gpu_type: str = Field(index=True)
|
||||
price_per_hour: float
|
||||
status: str = Field(default="active", index=True) # active, inactive, booked
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.utcnow())
|
||||
updated_at: datetime = Field(default_factory=lambda: datetime.utcnow())
|
||||
|
||||
# GPU specifications
|
||||
memory_gb: int | None = Field(default=None)
|
||||
cuda_version: str = Field(default="")
|
||||
region: str = Field(default="")
|
||||
|
||||
# Capabilities
|
||||
capabilities: list = Field(default_factory=list, sa_column=Column(JSON, nullable=True))
|
||||
|
||||
# Listing metadata
|
||||
extra_data: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=True))
|
||||
54
apps/aitbc-edge/src/aitbc_edge/schemas/island.py
Normal file
54
apps/aitbc-edge/src/aitbc_edge/schemas/island.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""Island-related schemas for Edge API Service"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import JSON, Column, Enum as SQLEnum
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class IslandStatus(StrEnum):
|
||||
"""Island membership status"""
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
BRIDGING = "bridging"
|
||||
JOINED = "joined"
|
||||
|
||||
|
||||
class IslandMembership(SQLModel, table=True):
|
||||
"""Island membership in edge API database"""
|
||||
|
||||
__tablename__ = "island_memberships"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: str = Field(default_factory=lambda: f"membership_{uuid4().hex[:8]}", primary_key=True)
|
||||
island_id: str = Field(sa_column=Column(index=True))
|
||||
island_name: str
|
||||
chain_id: str = Field(sa_column=Column(index=True))
|
||||
status: IslandStatus = Field(
|
||||
default=IslandStatus.ACTIVE,
|
||||
sa_column=Column(SQLEnum(IslandStatus, values_only=True), index=True)
|
||||
)
|
||||
role: str = Field(default="compute-provider") # compute-provider, consumer, hub
|
||||
joined_at: datetime = Field(default_factory=lambda: datetime.utcnow())
|
||||
peer_count: int = Field(default=0)
|
||||
|
||||
# Additional metadata
|
||||
extra_data: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=True))
|
||||
|
||||
|
||||
class BridgeRequest(SQLModel, table=True):
|
||||
"""Bridge request for island connectivity"""
|
||||
|
||||
__tablename__ = "bridge_requests"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: str = Field(default_factory=lambda: f"bridge_req_{uuid4().hex[:8]}", primary_key=True)
|
||||
request_id: str = Field(index=True)
|
||||
source_island_id: str
|
||||
target_island_id: str
|
||||
source_node_id: str
|
||||
status: str = Field(default="pending", index=True) # pending, approved, rejected
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.utcnow())
|
||||
updated_at: datetime = Field(default_factory=lambda: datetime.utcnow())
|
||||
21
apps/aitbc-edge/src/aitbc_edge/schemas/metrics.py
Normal file
21
apps/aitbc-edge/src/aitbc_edge/schemas/metrics.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Edge metrics-related schemas for Edge API Service"""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import JSON, Column
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class EdgeMetrics(SQLModel, table=True):
|
||||
"""Edge performance metrics"""
|
||||
|
||||
__tablename__ = "edge_metrics"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: str = Field(default_factory=lambda: f"edge_metric_{uuid4().hex[:8]}", primary_key=True)
|
||||
metric_id: str = Field(index=True)
|
||||
gpu_id: str = Field(index=True)
|
||||
metrics_data: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=False))
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
extra_data: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=True))
|
||||
45
apps/aitbc-edge/src/aitbc_edge/schemas/serve.py
Normal file
45
apps/aitbc-edge/src/aitbc_edge/schemas/serve.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""Edge serve-related schemas for Edge API Service"""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import JSON, Column
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class ComputeRequest(SQLModel, table=True):
|
||||
"""Compute request in edge serve queue"""
|
||||
|
||||
__tablename__ = "compute_requests"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: str = Field(default_factory=lambda: f"compute_req_{uuid4().hex[:8]}", primary_key=True)
|
||||
request_id: str = Field(index=True)
|
||||
gpu_id: str = Field(index=True)
|
||||
model_name: str
|
||||
input_data: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=True))
|
||||
priority: str = Field(default="normal")
|
||||
status: str = Field(default="queued", index=True) # queued, running, completed, failed, cancelled
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
started_at: datetime | None = Field(default=None)
|
||||
completed_at: datetime | None = Field(default=None)
|
||||
error: str | None = Field(default=None)
|
||||
extra_data: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=True))
|
||||
|
||||
|
||||
class ComputeResult(SQLModel, table=True):
|
||||
"""Compute result cache"""
|
||||
|
||||
__tablename__ = "compute_results"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: str = Field(default_factory=lambda: f"compute_res_{uuid4().hex[:8]}", primary_key=True)
|
||||
result_id: str = Field(index=True)
|
||||
request_id: str = Field(index=True)
|
||||
island_id: str = Field(index=True)
|
||||
gpu_id: str
|
||||
result: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=False))
|
||||
cache_ttl: int = Field(default=3600) # 1 hour default
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
expires_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
15
apps/aitbc-edge/src/aitbc_edge/services/__init__.py
Normal file
15
apps/aitbc-edge/src/aitbc_edge/services/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Services for Edge API Service"""
|
||||
|
||||
from .island_service import IslandService
|
||||
from .gpu_service import GPUService
|
||||
from .database_service import DatabaseService
|
||||
from .serve_service import ServeService
|
||||
from .metrics_service import MetricsService
|
||||
|
||||
__all__ = [
|
||||
"IslandService",
|
||||
"GPUService",
|
||||
"DatabaseService",
|
||||
"ServeService",
|
||||
"MetricsService",
|
||||
]
|
||||
132
apps/aitbc-edge/src/aitbc_edge/services/database_service.py
Normal file
132
apps/aitbc-edge/src/aitbc_edge/services/database_service.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""Database service for Edge API Service"""
|
||||
|
||||
from typing import Dict, Optional, List
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from ..storage import get_session
|
||||
from ..schemas.database import EdgeDatabase
|
||||
from sqlmodel import select, delete
|
||||
|
||||
|
||||
class DatabaseService:
|
||||
"""Service for edge database operations"""
|
||||
|
||||
async def init_database(self, database_id: str, island_id: str, capacity_gb: int) -> Dict:
|
||||
"""Initialize edge database"""
|
||||
async with get_session() as session:
|
||||
# Check if database already exists
|
||||
result = await session.execute(select(EdgeDatabase).where(EdgeDatabase.database_id == database_id))
|
||||
existing_db = result.scalar_one_or_none()
|
||||
|
||||
if existing_db:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"Database {database_id} already exists",
|
||||
"database": existing_db.database_id
|
||||
}
|
||||
|
||||
# Create new database record
|
||||
db = EdgeDatabase(
|
||||
database_id=database_id,
|
||||
island_id=island_id,
|
||||
capacity_gb=capacity_gb,
|
||||
used_gb=0,
|
||||
status="initialized",
|
||||
sync_status="idle",
|
||||
records_synced=0
|
||||
)
|
||||
session.add(db)
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Database {database_id} initialized",
|
||||
"database": database_id,
|
||||
"id": db.id
|
||||
}
|
||||
|
||||
async def get_database(self, database_id: str) -> Optional[Dict]:
|
||||
"""Get database details"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(select(EdgeDatabase).where(EdgeDatabase.database_id == database_id))
|
||||
db = result.scalar_one_or_none()
|
||||
|
||||
if db:
|
||||
return {
|
||||
"id": db.id,
|
||||
"database_id": db.database_id,
|
||||
"island_id": db.island_id,
|
||||
"capacity_gb": db.capacity_gb,
|
||||
"used_gb": db.used_gb,
|
||||
"status": db.status,
|
||||
"created_at": db.created_at.isoformat() if db.created_at else None,
|
||||
"updated_at": db.updated_at.isoformat() if db.updated_at else None,
|
||||
"last_sync_at": db.last_sync_at.isoformat() if db.last_sync_at else None,
|
||||
"sync_status": db.sync_status,
|
||||
"records_synced": db.records_synced,
|
||||
"extra_data": db.extra_data
|
||||
}
|
||||
return None
|
||||
|
||||
async def delete_database(self, database_id: str) -> bool:
|
||||
"""Delete database"""
|
||||
async with get_session() as session:
|
||||
stmt = delete(EdgeDatabase).where(EdgeDatabase.database_id == database_id)
|
||||
result = await session.execute(stmt)
|
||||
await session.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
async def sync_database(self, database_id: str) -> Dict:
|
||||
"""Sync database from source"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(select(EdgeDatabase).where(EdgeDatabase.database_id == database_id))
|
||||
db = result.scalar_one_or_none()
|
||||
|
||||
if not db:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"Database {database_id} not found"
|
||||
}
|
||||
|
||||
# Update sync status in single transaction
|
||||
db.sync_status = "syncing"
|
||||
db.updated_at = datetime.utcnow()
|
||||
|
||||
# Simulate sync process (in production, this would actually sync data)
|
||||
db.sync_status = "idle"
|
||||
db.last_sync_at = datetime.utcnow()
|
||||
db.records_synced = db.records_synced + 100 # Simulated
|
||||
db.updated_at = datetime.utcnow()
|
||||
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Database {database_id} synced",
|
||||
"records_synced": db.records_synced
|
||||
}
|
||||
|
||||
async def list_databases(self, island_id: str = None) -> List[Dict]:
|
||||
"""List databases, optionally filtered by island_id"""
|
||||
async with get_session() as session:
|
||||
if island_id:
|
||||
result = await session.execute(select(EdgeDatabase).where(EdgeDatabase.island_id == island_id))
|
||||
else:
|
||||
result = await session.execute(select(EdgeDatabase))
|
||||
databases = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": db.id,
|
||||
"database_id": db.database_id,
|
||||
"island_id": db.island_id,
|
||||
"capacity_gb": db.capacity_gb,
|
||||
"used_gb": db.used_gb,
|
||||
"status": db.status,
|
||||
"sync_status": db.sync_status,
|
||||
"records_synced": db.records_synced,
|
||||
"created_at": db.created_at.isoformat() if db.created_at else None
|
||||
}
|
||||
for db in databases
|
||||
]
|
||||
81
apps/aitbc-edge/src/aitbc_edge/services/gpu_service.py
Normal file
81
apps/aitbc-edge/src/aitbc_edge/services/gpu_service.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""GPU service for Edge API Service"""
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from ..clients.gpu_service import GPUServiceClient
|
||||
from ..storage import get_session
|
||||
from ..schemas.gpu import GPUListing
|
||||
|
||||
|
||||
class GPUService:
|
||||
"""Service for GPU operations"""
|
||||
|
||||
def __init__(self):
|
||||
self.gpu_client = GPUServiceClient()
|
||||
|
||||
async def list_gpus(self, architecture: str = None, edge_optimized: bool = None, min_memory_gb: int = None) -> List[Dict]:
|
||||
"""List GPUs via GPU service"""
|
||||
profiles = await self.gpu_client.get_gpu_profiles(architecture, edge_optimized, min_memory_gb)
|
||||
|
||||
# Store GPU listings in edge-api database
|
||||
async with get_session() as session:
|
||||
for profile in profiles:
|
||||
gpu_listing = GPUListing(
|
||||
gpu_id=profile.get("id", ""),
|
||||
model=profile.get("model", "Unknown"),
|
||||
memory_gb=profile.get("memory_gb", 0),
|
||||
cuda_version=profile.get("cuda_version", ""),
|
||||
region=profile.get("region", ""),
|
||||
capabilities=profile.get("capabilities", []),
|
||||
extra_data=profile
|
||||
)
|
||||
session.add(gpu_listing)
|
||||
await session.commit()
|
||||
|
||||
return profiles
|
||||
|
||||
async def get_gpu_listing(self, gpu_id: str) -> Optional[Dict]:
|
||||
"""Get GPU listing details"""
|
||||
# Get from GPU service
|
||||
try:
|
||||
profiles = await self.gpu_client.get_gpu_profiles()
|
||||
for profile in profiles:
|
||||
if profile.get("id") == gpu_id:
|
||||
return profile
|
||||
return None
|
||||
except Exception as e:
|
||||
# Fall back to database
|
||||
from sqlmodel import select
|
||||
async with get_session() as session:
|
||||
result = await session.execute(select(GPUListing).where(GPUListing.gpu_id == gpu_id))
|
||||
gpu = result.scalar_one_or_none()
|
||||
if gpu:
|
||||
return {
|
||||
"id": gpu.gpu_id,
|
||||
"model": gpu.model,
|
||||
"memory_gb": gpu.memory_gb,
|
||||
"cuda_version": gpu.cuda_version,
|
||||
"region": gpu.region,
|
||||
"capabilities": gpu.capabilities,
|
||||
"extra_data": gpu.extra_data
|
||||
}
|
||||
return None
|
||||
|
||||
async def remove_gpu_listing(self, gpu_id: str) -> bool:
|
||||
"""Remove GPU listing from database"""
|
||||
from sqlmodel import delete
|
||||
async with get_session() as session:
|
||||
stmt = delete(GPUListing).where(GPUListing.gpu_id == gpu_id)
|
||||
result = await session.execute(stmt)
|
||||
await session.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
async def scan_gpus(self, miner_id: str) -> Dict:
|
||||
"""Scan GPUs via GPU service"""
|
||||
result = await self.gpu_client.scan_gpus(miner_id)
|
||||
return result
|
||||
|
||||
async def get_gpu_metrics(self, gpu_id: str, limit: int = 100) -> List[Dict]:
|
||||
"""Get GPU metrics via GPU service"""
|
||||
metrics = await self.gpu_client.get_gpu_metrics(gpu_id, limit)
|
||||
return metrics
|
||||
87
apps/aitbc-edge/src/aitbc_edge/services/island_service.py
Normal file
87
apps/aitbc-edge/src/aitbc_edge/services/island_service.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Island service for Edge API Service"""
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from ..clients.blockchain_rpc import BlockchainRPCClient
|
||||
from ..storage import get_session
|
||||
from ..schemas.island import IslandMembership, BridgeRequest, IslandStatus
|
||||
|
||||
|
||||
class IslandService:
|
||||
"""Service for island operations"""
|
||||
|
||||
def __init__(self):
|
||||
self.rpc_client = BlockchainRPCClient()
|
||||
|
||||
async def join_island(self, island_id: str, island_name: str, chain_id: str, role: str = "compute-provider", is_hub: bool = False) -> Dict:
|
||||
"""Join an island via blockchain RPC"""
|
||||
# Call blockchain RPC to join island
|
||||
result = await self.rpc_client.join_island(island_id, island_name, chain_id, role, is_hub)
|
||||
|
||||
# Store membership in edge-api database
|
||||
if result.get("success"):
|
||||
async with get_session() as session:
|
||||
# Map blockchain status string to IslandStatus enum
|
||||
# PostgreSQL enum only has: active, inactive, bridging
|
||||
# Map "joined" to "active"
|
||||
raw_status = result.get("status", "active").lower()
|
||||
if raw_status == "joined":
|
||||
raw_status = "active"
|
||||
try:
|
||||
status = IslandStatus(raw_status)
|
||||
except ValueError:
|
||||
status = IslandStatus.ACTIVE
|
||||
membership = IslandMembership(
|
||||
island_id=island_id,
|
||||
island_name=island_name,
|
||||
chain_id=chain_id,
|
||||
role=role,
|
||||
status=status
|
||||
)
|
||||
session.add(membership)
|
||||
await session.commit()
|
||||
|
||||
return result
|
||||
|
||||
async def leave_island(self, island_id: str) -> Dict:
|
||||
"""Leave an island via blockchain RPC"""
|
||||
# Call blockchain RPC to leave island
|
||||
result = await self.rpc_client.leave_island(island_id)
|
||||
|
||||
# Remove membership from edge-api database
|
||||
if result.get("success"):
|
||||
async with get_session() as session:
|
||||
from sqlmodel import delete
|
||||
stmt = delete(IslandMembership).where(IslandMembership.island_id == island_id)
|
||||
await session.execute(stmt)
|
||||
await session.commit()
|
||||
|
||||
return result
|
||||
|
||||
async def list_islands(self) -> List[Dict]:
|
||||
"""List all islands via blockchain RPC"""
|
||||
result = await self.rpc_client.list_islands()
|
||||
return result.get("islands", [])
|
||||
|
||||
async def get_island(self, island_id: str) -> Optional[Dict]:
|
||||
"""Get island details via blockchain RPC"""
|
||||
result = await self.rpc_client.get_island_info(island_id)
|
||||
return result
|
||||
|
||||
async def request_bridge(self, target_island_id: str) -> Dict:
|
||||
"""Request bridge to another island via blockchain RPC"""
|
||||
result = await self.rpc_client.request_bridge(target_island_id)
|
||||
|
||||
# Store bridge request in edge-api database
|
||||
if result.get("success"):
|
||||
async with get_session() as session:
|
||||
bridge_req = BridgeRequest(
|
||||
request_id=result.get("request_id"),
|
||||
target_island_id=target_island_id,
|
||||
source_node_id="edge-api", # TODO: Get actual node ID
|
||||
status=result.get("status", "pending")
|
||||
)
|
||||
session.add(bridge_req)
|
||||
await session.commit()
|
||||
|
||||
return result
|
||||
79
apps/aitbc-edge/src/aitbc_edge/services/metrics_service.py
Normal file
79
apps/aitbc-edge/src/aitbc_edge/services/metrics_service.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Edge metrics service for Edge API Service"""
|
||||
|
||||
from typing import Dict, Optional, List
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from ..storage import get_session
|
||||
from ..schemas.metrics import EdgeMetrics
|
||||
from sqlmodel import select, delete
|
||||
|
||||
|
||||
class MetricsService:
|
||||
"""Service for edge metrics operations"""
|
||||
|
||||
async def record_metrics(self, gpu_id: str, metrics: dict) -> Dict:
|
||||
"""Record edge metrics"""
|
||||
async with get_session() as session:
|
||||
metric_id = f"metric_{uuid4().hex[:8]}"
|
||||
|
||||
metric = EdgeMetrics(
|
||||
metric_id=metric_id,
|
||||
gpu_id=gpu_id,
|
||||
metrics_data=metrics
|
||||
)
|
||||
session.add(metric)
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"metric_id": metric_id,
|
||||
"message": f"Metrics {metric_id} recorded"
|
||||
}
|
||||
|
||||
async def get_metrics(self, metric_id: str) -> Optional[Dict]:
|
||||
"""Get metric details"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(select(EdgeMetrics).where(EdgeMetrics.metric_id == metric_id))
|
||||
metric = result.scalar_one_or_none()
|
||||
|
||||
if metric:
|
||||
return {
|
||||
"metric_id": metric.metric_id,
|
||||
"gpu_id": metric.gpu_id,
|
||||
"metrics_data": metric.metrics_data,
|
||||
"created_at": metric.created_at.isoformat() if metric.created_at else None,
|
||||
"extra_data": metric.extra_data
|
||||
}
|
||||
return None
|
||||
|
||||
async def list_metrics(self, gpu_id: str = None, limit: int = 100) -> List[Dict]:
|
||||
"""List metrics, optionally filtered by gpu_id"""
|
||||
async with get_session() as session:
|
||||
query = select(EdgeMetrics)
|
||||
|
||||
if gpu_id:
|
||||
query = query.where(EdgeMetrics.gpu_id == gpu_id)
|
||||
|
||||
query = query.order_by(EdgeMetrics.created_at.desc()).limit(limit)
|
||||
|
||||
result = await session.execute(query)
|
||||
metrics = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"metric_id": metric.metric_id,
|
||||
"gpu_id": metric.gpu_id,
|
||||
"metrics_data": metric.metrics_data,
|
||||
"created_at": metric.created_at.isoformat() if metric.created_at else None
|
||||
}
|
||||
for metric in metrics
|
||||
]
|
||||
|
||||
async def delete_metrics(self, metric_id: str) -> bool:
|
||||
"""Delete metric"""
|
||||
async with get_session() as session:
|
||||
stmt = delete(EdgeMetrics).where(EdgeMetrics.metric_id == metric_id)
|
||||
result = await session.execute(stmt)
|
||||
await session.commit()
|
||||
return result.rowcount > 0
|
||||
114
apps/aitbc-edge/src/aitbc_edge/services/serve_service.py
Normal file
114
apps/aitbc-edge/src/aitbc_edge/services/serve_service.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""Edge serve service for Edge API Service"""
|
||||
|
||||
from typing import Dict, Optional, List
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from ..storage import get_session
|
||||
from ..schemas.serve import ComputeRequest, ComputeResult
|
||||
from sqlmodel import select, delete
|
||||
|
||||
|
||||
class ServeService:
|
||||
"""Service for edge serve operations"""
|
||||
|
||||
async def submit_compute_request(self, gpu_id: str, model_name: str, input_data: dict, priority: str = "normal") -> Dict:
|
||||
"""Submit compute request"""
|
||||
async with get_session() as session:
|
||||
request_id = f"req_{uuid4().hex[:8]}"
|
||||
|
||||
request = ComputeRequest(
|
||||
request_id=request_id,
|
||||
gpu_id=gpu_id,
|
||||
model_name=model_name,
|
||||
input_data=input_data,
|
||||
priority=priority,
|
||||
status="queued"
|
||||
)
|
||||
session.add(request)
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"request_id": request_id,
|
||||
"status": "queued",
|
||||
"message": f"Compute request {request_id} submitted"
|
||||
}
|
||||
|
||||
async def get_compute_request(self, request_id: str) -> Optional[Dict]:
|
||||
"""Get compute request details"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(select(ComputeRequest).where(ComputeRequest.request_id == request_id))
|
||||
req = result.scalar_one_or_none()
|
||||
|
||||
if req:
|
||||
return {
|
||||
"request_id": req.request_id,
|
||||
"gpu_id": req.gpu_id,
|
||||
"model_name": req.model_name,
|
||||
"input_data": req.input_data,
|
||||
"priority": req.priority,
|
||||
"status": req.status,
|
||||
"created_at": req.created_at.isoformat() if req.created_at else None,
|
||||
"started_at": req.started_at.isoformat() if req.started_at else None,
|
||||
"completed_at": req.completed_at.isoformat() if req.completed_at else None,
|
||||
"error": req.error,
|
||||
"extra_data": req.extra_data
|
||||
}
|
||||
return None
|
||||
|
||||
async def cancel_compute_request(self, request_id: str) -> bool:
|
||||
"""Cancel compute request"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(select(ComputeRequest).where(ComputeRequest.request_id == request_id))
|
||||
req = result.scalar_one_or_none()
|
||||
|
||||
if req and req.status in ["queued", "running"]:
|
||||
req.status = "cancelled"
|
||||
req.completed_at = datetime.utcnow()
|
||||
await session.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
async def list_compute_requests(self, gpu_id: str = None, status: str = None) -> List[Dict]:
|
||||
"""List compute requests, optionally filtered"""
|
||||
async with get_session() as session:
|
||||
query = select(ComputeRequest)
|
||||
|
||||
if gpu_id:
|
||||
query = query.where(ComputeRequest.gpu_id == gpu_id)
|
||||
if status:
|
||||
query = query.where(ComputeRequest.status == status)
|
||||
|
||||
result = await session.execute(query)
|
||||
requests = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"request_id": req.request_id,
|
||||
"gpu_id": req.gpu_id,
|
||||
"model_name": req.model_name,
|
||||
"priority": req.priority,
|
||||
"status": req.status,
|
||||
"created_at": req.created_at.isoformat() if req.created_at else None
|
||||
}
|
||||
for req in requests
|
||||
]
|
||||
|
||||
async def get_compute_result(self, request_id: str) -> Optional[Dict]:
|
||||
"""Get compute result"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(select(ComputeResult).where(ComputeResult.request_id == request_id))
|
||||
res = result.scalar_one_or_none()
|
||||
|
||||
if res:
|
||||
return {
|
||||
"result_id": res.result_id,
|
||||
"request_id": res.request_id,
|
||||
"output_data": res.output_data,
|
||||
"metrics": res.metrics,
|
||||
"status": res.status,
|
||||
"created_at": res.created_at.isoformat() if res.created_at else None,
|
||||
"extra_data": res.extra_data
|
||||
}
|
||||
return None
|
||||
46
apps/aitbc-edge/src/aitbc_edge/storage.py
Normal file
46
apps/aitbc-edge/src/aitbc_edge/storage.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Database session management for Edge API Service"""
|
||||
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncIterator
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
from aitbc import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Database URL from environment variable or default
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://aitbc_edge:password@localhost:5432/aitbc_edge")
|
||||
|
||||
# Create async engine with proper connection pool settings
|
||||
engine = create_async_engine(
|
||||
DATABASE_URL,
|
||||
echo=False,
|
||||
pool_size=10,
|
||||
max_overflow=20,
|
||||
pool_pre_ping=True, # Verify connections before using
|
||||
pool_recycle=3600, # Recycle connections after 1 hour
|
||||
)
|
||||
|
||||
|
||||
async def init_db() -> None:
|
||||
"""Initialize database tables"""
|
||||
from .schemas.island import IslandMembership, BridgeRequest
|
||||
from .schemas.gpu import GPUListing
|
||||
from .schemas.database import EdgeDatabase
|
||||
from .schemas.serve import ComputeRequest, ComputeResult
|
||||
from .schemas.metrics import EdgeMetrics
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(SQLModel.metadata.create_all)
|
||||
|
||||
logger.info("Edge API database initialized")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_session() -> AsyncIterator[AsyncSession]:
|
||||
"""Get database session"""
|
||||
async with AsyncSession(engine) as session:
|
||||
yield session
|
||||
Reference in New Issue
Block a user