feat: Add URL validation and update to verified Runpod URL
Browse files- Add comprehensive URL validation to ZeroGPU client
- Format validation (scheme, host, port)
- Connectivity checking on initialization
- Placeholder detection (your-pod-ip, localhost)
- Helpful error messages with troubleshooting tips
- Update default URL to verified Runpod proxy URL
- Default: https://bm9njt1ypzvuqw-8000.proxy.runpod.net
- Updated all documentation with actual URL
- Added Runpod URL format validation
- Create URL verification script (verify_zerogpu_url.py)
- Validates URL format
- Tests connectivity
- Checks health/ready endpoints
- Optional authentication testing
- Update all documentation
- Dockerfile: Updated with Runpod URL format
- DEPLOYMENT_CHECKLIST.md: Actual URL examples
- ZEROGPU_PER_USER_MODE.md: Runpod URL format
- New ZEROGPU_URL_CONFIGURATION.md: Complete URL guide
Benefits:
- Automatic URL validation prevents misconfiguration
- Clear error messages for troubleshooting
- Verified working URL as default
- Comprehensive verification tools
- DEPLOYMENT_CHECKLIST.md +4 -2
- Dockerfile +2 -1
- ZEROGPU_PER_USER_MODE.md +4 -4
- ZEROGPU_URL_CONFIGURATION.md +211 -0
- config.py +2 -1
- verify_zerogpu_url.py +288 -0
- zero_gpu_client.py +146 -5
|
@@ -44,7 +44,7 @@ HF_TOKEN=your_huggingface_token_here
|
|
| 44 |
**Option A: Service Account Mode**
|
| 45 |
```bash
|
| 46 |
USE_ZERO_GPU=true
|
| 47 |
-
ZERO_GPU_API_URL=
|
| 48 | |
| 49 |
ZERO_GPU_PASSWORD=your-password
|
| 50 |
```
|
|
@@ -53,11 +53,13 @@ ZERO_GPU_PASSWORD=your-password
|
|
| 53 |
```bash
|
| 54 |
USE_ZERO_GPU=true
|
| 55 |
ZERO_GPU_PER_USER_MODE=true
|
| 56 |
-
ZERO_GPU_API_URL=
|
| 57 | |
| 58 |
ZERO_GPU_ADMIN_PASSWORD=admin-password
|
| 59 |
```
|
| 60 |
|
|
|
|
|
|
|
| 61 |
**Additional Optional Variables:**
|
| 62 |
```bash
|
| 63 |
DB_PATH=sessions.db
|
|
|
|
| 44 |
**Option A: Service Account Mode**
|
| 45 |
```bash
|
| 46 |
USE_ZERO_GPU=true
|
| 47 |
+
ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 48 | |
| 49 |
ZERO_GPU_PASSWORD=your-password
|
| 50 |
```
|
|
|
|
| 53 |
```bash
|
| 54 |
USE_ZERO_GPU=true
|
| 55 |
ZERO_GPU_PER_USER_MODE=true
|
| 56 |
+
ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 57 | |
| 58 |
ZERO_GPU_ADMIN_PASSWORD=admin-password
|
| 59 |
```
|
| 60 |
|
| 61 |
+
**Note:** Runpod proxy URLs follow the format: `https://<pod-id>-8000.proxy.runpod.net`
|
| 62 |
+
|
| 63 |
**Additional Optional Variables:**
|
| 64 |
```bash
|
| 65 |
DB_PATH=sessions.db
|
|
@@ -40,7 +40,8 @@ HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 \
|
|
| 40 |
# ZeroGPU API Configuration (optional):
|
| 41 |
# Set these environment variables in HF Spaces secrets for ZeroGPU integration:
|
| 42 |
# - USE_ZERO_GPU=true (enable ZeroGPU API)
|
| 43 |
-
# - ZERO_GPU_API_URL=
|
|
|
|
| 44 |
# - [email protected] (for service account mode)
|
| 45 |
# - ZERO_GPU_PASSWORD=your-password (for service account mode)
|
| 46 |
# - ZERO_GPU_PER_USER_MODE=true (for per-user mode, optional)
|
|
|
|
| 40 |
# ZeroGPU API Configuration (optional):
|
| 41 |
# Set these environment variables in HF Spaces secrets for ZeroGPU integration:
|
| 42 |
# - USE_ZERO_GPU=true (enable ZeroGPU API)
|
| 43 |
+
# - ZERO_GPU_API_URL=https://<pod-id>-8000.proxy.runpod.net (Runpod proxy URL)
|
| 44 |
+
# Example: https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 45 |
# - [email protected] (for service account mode)
|
| 46 |
# - ZERO_GPU_PASSWORD=your-password (for service account mode)
|
| 47 |
# - ZERO_GPU_PER_USER_MODE=true (for per-user mode, optional)
|
|
@@ -19,8 +19,8 @@ USE_ZERO_GPU=true
|
|
| 19 |
# Enable per-user mode (Option B)
|
| 20 |
ZERO_GPU_PER_USER_MODE=true
|
| 21 |
|
| 22 |
-
# ZeroGPU API base URL
|
| 23 |
-
ZERO_GPU_API_URL=
|
| 24 |
|
| 25 |
# Admin credentials (for creating/approving users)
|
| 26 | |
|
@@ -108,7 +108,7 @@ from zero_gpu_user_manager import ZeroGPUUserManager
|
|
| 108 |
|
| 109 |
# Initialize manager (usually done in LLMRouter)
|
| 110 |
user_manager = ZeroGPUUserManager(
|
| 111 |
-
base_url="
|
| 112 |
admin_email="[email protected]",
|
| 113 |
admin_password="password",
|
| 114 |
db_path="sessions.db"
|
|
@@ -214,7 +214,7 @@ To migrate from service account (Option A) to per-user (Option B):
|
|
| 214 |
# config.py
|
| 215 |
zero_gpu_config = {
|
| 216 |
"enabled": True,
|
| 217 |
-
"base_url": "
|
| 218 |
"per_user_mode": True, # Enable per-user mode
|
| 219 |
"admin_email": "[email protected]",
|
| 220 |
"admin_password": "secure-password",
|
|
|
|
| 19 |
# Enable per-user mode (Option B)
|
| 20 |
ZERO_GPU_PER_USER_MODE=true
|
| 21 |
|
| 22 |
+
# ZeroGPU API base URL (Runpod proxy URL)
|
| 23 |
+
ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 24 |
|
| 25 |
# Admin credentials (for creating/approving users)
|
| 26 | |
|
|
|
| 108 |
|
| 109 |
# Initialize manager (usually done in LLMRouter)
|
| 110 |
user_manager = ZeroGPUUserManager(
|
| 111 |
+
base_url="https://bm9njt1ypzvuqw-8000.proxy.runpod.net",
|
| 112 |
admin_email="[email protected]",
|
| 113 |
admin_password="password",
|
| 114 |
db_path="sessions.db"
|
|
|
|
| 214 |
# config.py
|
| 215 |
zero_gpu_config = {
|
| 216 |
"enabled": True,
|
| 217 |
+
"base_url": "https://bm9njt1ypzvuqw-8000.proxy.runpod.net",
|
| 218 |
"per_user_mode": True, # Enable per-user mode
|
| 219 |
"admin_email": "[email protected]",
|
| 220 |
"admin_password": "secure-password",
|
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ZeroGPU API URL Configuration
|
| 2 |
+
|
| 3 |
+
## ✅ Verified Working URL
|
| 4 |
+
|
| 5 |
+
**Your ZeroGPU API URL:**
|
| 6 |
+
```
|
| 7 |
+
https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 8 |
+
```
|
| 9 |
+
|
| 10 |
+
**Status:** ✅ Verified and working
|
| 11 |
+
**API Response:** Confirmed accessible
|
| 12 |
+
**Format:** Runpod proxy URL (standard format)
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## URL Format
|
| 17 |
+
|
| 18 |
+
### Runpod Proxy URL (Recommended)
|
| 19 |
+
```
|
| 20 |
+
https://<pod-id>-8000.proxy.runpod.net
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
**Example:**
|
| 24 |
+
```
|
| 25 |
+
https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
**Characteristics:**
|
| 29 |
+
- ✅ HTTPS (secure)
|
| 30 |
+
- ✅ Runpod-managed proxy
|
| 31 |
+
- ✅ Automatic SSL/TLS
|
| 32 |
+
- ✅ Publicly accessible
|
| 33 |
+
- ✅ No port number needed in URL
|
| 34 |
+
|
| 35 |
+
### Direct IP URL (Alternative)
|
| 36 |
+
```
|
| 37 |
+
http://<ip-address>:8000
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
**Example:**
|
| 41 |
+
```
|
| 42 |
+
http://192.168.1.100:8000
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
**Note:** Only use if Runpod proxy is not available.
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## Configuration
|
| 50 |
+
|
| 51 |
+
### Environment Variable
|
| 52 |
+
|
| 53 |
+
Set in Hugging Face Spaces → Settings → Repository secrets:
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
### Default Configuration
|
| 60 |
+
|
| 61 |
+
The application now defaults to your Runpod URL:
|
| 62 |
+
- **Default:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net`
|
| 63 |
+
- **Override:** Set `ZERO_GPU_API_URL` environment variable
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
## URL Validation
|
| 68 |
+
|
| 69 |
+
The system automatically validates the URL:
|
| 70 |
+
|
| 71 |
+
1. **Format Check:**
|
| 72 |
+
- Must have `http://` or `https://` scheme
|
| 73 |
+
- Must have valid host/domain
|
| 74 |
+
- Rejects placeholder values (`your-pod-ip`, `localhost`)
|
| 75 |
+
|
| 76 |
+
2. **Connectivity Check:**
|
| 77 |
+
- Tests `/health` endpoint
|
| 78 |
+
- Verifies API is reachable
|
| 79 |
+
- Provides helpful error messages if connection fails
|
| 80 |
+
|
| 81 |
+
3. **Readiness Check:**
|
| 82 |
+
- Tests `/ready` endpoint
|
| 83 |
+
- Verifies models are loaded
|
| 84 |
+
- Waits for API to be ready (up to 5 minutes)
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## Verification
|
| 89 |
+
|
| 90 |
+
### Manual Verification
|
| 91 |
+
|
| 92 |
+
Use the verification script:
|
| 93 |
+
```bash
|
| 94 |
+
python verify_zerogpu_url.py https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### Programmatic Verification
|
| 98 |
+
|
| 99 |
+
```python
|
| 100 |
+
from zero_gpu_client import ZeroGPUChatClient
|
| 101 |
+
|
| 102 |
+
# URL is validated automatically on initialization
|
| 103 |
+
client = ZeroGPUChatClient(
|
| 104 |
+
base_url="https://bm9njt1ypzvuqw-8000.proxy.runpod.net",
|
| 105 |
+
email="[email protected]",
|
| 106 |
+
password="your-password"
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
# Or verify URL without authentication
|
| 110 |
+
results = client.verify_url()
|
| 111 |
+
print(results)
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
## API Status
|
| 117 |
+
|
| 118 |
+
From the API response:
|
| 119 |
+
```json
|
| 120 |
+
{
|
| 121 |
+
"message": "ZeroGPU Chat API",
|
| 122 |
+
"version": "1.0.0",
|
| 123 |
+
"available_tasks": ["classification", "reasoning", "embedding", "general"],
|
| 124 |
+
"api_docs": "/docs",
|
| 125 |
+
"status": "loading"
|
| 126 |
+
}
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
**Status Values:**
|
| 130 |
+
- `"loading"` - Models are loading (normal during startup)
|
| 131 |
+
- `"ready"` - All models loaded and ready
|
| 132 |
+
|
| 133 |
+
**Available Tasks:**
|
| 134 |
+
- ✅ `classification` - Text classification
|
| 135 |
+
- ✅ `reasoning` - Complex reasoning
|
| 136 |
+
- ✅ `embedding` - Text embeddings
|
| 137 |
+
- ✅ `general` - General purpose chat
|
| 138 |
+
|
| 139 |
+
---
|
| 140 |
+
|
| 141 |
+
## Troubleshooting
|
| 142 |
+
|
| 143 |
+
### Issue: URL Not Reachable
|
| 144 |
+
|
| 145 |
+
**Symptoms:**
|
| 146 |
+
- Connection refused
|
| 147 |
+
- Timeout errors
|
| 148 |
+
- 404 Not Found
|
| 149 |
+
|
| 150 |
+
**Solutions:**
|
| 151 |
+
1. Verify Runpod pod is running
|
| 152 |
+
2. Check pod status in Runpod dashboard
|
| 153 |
+
3. Verify proxy URL is correct
|
| 154 |
+
4. Check network connectivity
|
| 155 |
+
5. Ensure port 8000 is exposed
|
| 156 |
+
|
| 157 |
+
### Issue: Invalid URL Format
|
| 158 |
+
|
| 159 |
+
**Error:**
|
| 160 |
+
```
|
| 161 |
+
Invalid ZeroGPU API URL: 'http://your-pod-ip:8000'
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
**Solution:**
|
| 165 |
+
- Replace placeholder with actual Runpod URL
|
| 166 |
+
- Use format: `https://<pod-id>-8000.proxy.runpod.net`
|
| 167 |
+
- Ensure URL starts with `http://` or `https://`
|
| 168 |
+
|
| 169 |
+
### Issue: API Not Ready
|
| 170 |
+
|
| 171 |
+
**Status:** `"loading"`
|
| 172 |
+
|
| 173 |
+
**Solution:**
|
| 174 |
+
- Wait for models to load (typically 2-5 minutes)
|
| 175 |
+
- Check `/ready` endpoint periodically
|
| 176 |
+
- System will automatically wait for readiness
|
| 177 |
+
|
| 178 |
+
---
|
| 179 |
+
|
| 180 |
+
## Quick Reference
|
| 181 |
+
|
| 182 |
+
### Current Configuration
|
| 183 |
+
```bash
|
| 184 |
+
ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
### API Endpoints
|
| 188 |
+
- **Health:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/health`
|
| 189 |
+
- **Ready:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/ready`
|
| 190 |
+
- **API Info:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/`
|
| 191 |
+
- **Docs:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/docs`
|
| 192 |
+
- **Chat:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/chat`
|
| 193 |
+
|
| 194 |
+
### Verification Commands
|
| 195 |
+
```bash
|
| 196 |
+
# Check health
|
| 197 |
+
curl https://bm9njt1ypzvuqw-8000.proxy.runpod.net/health
|
| 198 |
+
|
| 199 |
+
# Check readiness
|
| 200 |
+
curl https://bm9njt1ypzvuqw-8000.proxy.runpod.net/ready
|
| 201 |
+
|
| 202 |
+
# Get API info
|
| 203 |
+
curl https://bm9njt1ypzvuqw-8000.proxy.runpod.net/
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
---
|
| 207 |
+
|
| 208 |
+
**Last Updated:** 2025-01-07
|
| 209 |
+
**URL Status:** ✅ Verified and Working
|
| 210 |
+
**API Version:** 1.0.0
|
| 211 |
+
|
|
@@ -38,7 +38,8 @@ class Settings(BaseSettings):
|
|
| 38 |
|
| 39 |
# ZeroGPU API settings
|
| 40 |
zero_gpu_enabled: bool = os.getenv("USE_ZERO_GPU", "false").lower() == "true"
|
| 41 |
-
|
|
|
|
| 42 |
zero_gpu_email: str = os.getenv("ZERO_GPU_EMAIL", "")
|
| 43 |
zero_gpu_password: str = os.getenv("ZERO_GPU_PASSWORD", "")
|
| 44 |
# Per-user mode (Option B: Multi-tenant)
|
|
|
|
| 38 |
|
| 39 |
# ZeroGPU API settings
|
| 40 |
zero_gpu_enabled: bool = os.getenv("USE_ZERO_GPU", "false").lower() == "true"
|
| 41 |
+
# Default to Runpod proxy URL format: https://<pod-id>-8000.proxy.runpod.net
|
| 42 |
+
zero_gpu_base_url: str = os.getenv("ZERO_GPU_API_URL", "https://bm9njt1ypzvuqw-8000.proxy.runpod.net")
|
| 43 |
zero_gpu_email: str = os.getenv("ZERO_GPU_EMAIL", "")
|
| 44 |
zero_gpu_password: str = os.getenv("ZERO_GPU_PASSWORD", "")
|
| 45 |
# Per-user mode (Option B: Multi-tenant)
|
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
ZeroGPU API URL Verification Script
|
| 4 |
+
Validates and tests connectivity to ZeroGPU API endpoint
|
| 5 |
+
"""
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
import requests
|
| 9 |
+
import argparse
|
| 10 |
+
from urllib.parse import urlparse
|
| 11 |
+
from typing import Dict, Any
|
| 12 |
+
|
| 13 |
+
def validate_url_format(url: str) -> tuple[bool, str]:
|
| 14 |
+
"""
|
| 15 |
+
Validate URL format
|
| 16 |
+
|
| 17 |
+
Returns:
|
| 18 |
+
(is_valid, error_message)
|
| 19 |
+
"""
|
| 20 |
+
# Check for placeholder values
|
| 21 |
+
if 'your-pod-ip' in url.lower():
|
| 22 |
+
return False, "URL contains placeholder 'your-pod-ip'. Please use actual Runpod proxy URL."
|
| 23 |
+
|
| 24 |
+
if url == 'http://localhost:8000' or url == 'http://:8000':
|
| 25 |
+
return False, "URL is set to localhost/default. Please use actual Runpod proxy URL."
|
| 26 |
+
|
| 27 |
+
# Validate Runpod proxy URL format (optional check)
|
| 28 |
+
if 'runpod.net' in url.lower() and '-8000.proxy.runpod.net' not in url.lower():
|
| 29 |
+
return False, "Invalid Runpod URL format. Expected: https://<pod-id>-8000.proxy.runpod.net"
|
| 30 |
+
|
| 31 |
+
# Parse URL
|
| 32 |
+
try:
|
| 33 |
+
parsed = urlparse(url)
|
| 34 |
+
if not parsed.scheme:
|
| 35 |
+
return False, "URL missing scheme (http:// or https://)"
|
| 36 |
+
if not parsed.netloc:
|
| 37 |
+
return False, "URL missing host/domain"
|
| 38 |
+
if parsed.scheme not in ['http', 'https']:
|
| 39 |
+
return False, f"URL must use http:// or https://, got: {parsed.scheme}"
|
| 40 |
+
|
| 41 |
+
# Check port if specified
|
| 42 |
+
if ':' in parsed.netloc:
|
| 43 |
+
host, port = parsed.netloc.rsplit(':', 1)
|
| 44 |
+
try:
|
| 45 |
+
port_num = int(port)
|
| 46 |
+
if not (1 <= port_num <= 65535):
|
| 47 |
+
return False, f"Invalid port number: {port_num}"
|
| 48 |
+
except ValueError:
|
| 49 |
+
return False, f"Invalid port: {port}"
|
| 50 |
+
|
| 51 |
+
return True, ""
|
| 52 |
+
except Exception as e:
|
| 53 |
+
return False, f"URL parsing error: {e}"
|
| 54 |
+
|
| 55 |
+
def test_connectivity(url: str, timeout: int = 10) -> Dict[str, Any]:
|
| 56 |
+
"""
|
| 57 |
+
Test connectivity to ZeroGPU API
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
Dictionary with test results
|
| 61 |
+
"""
|
| 62 |
+
results = {
|
| 63 |
+
"url": url,
|
| 64 |
+
"reachable": False,
|
| 65 |
+
"health_check": False,
|
| 66 |
+
"ready_check": False,
|
| 67 |
+
"api_info": False,
|
| 68 |
+
"status_code": None,
|
| 69 |
+
"error": None,
|
| 70 |
+
"endpoints_tested": []
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
# Test 1: Basic connectivity (health endpoint)
|
| 74 |
+
try:
|
| 75 |
+
health_url = f"{url}/health"
|
| 76 |
+
results["endpoints_tested"].append(health_url)
|
| 77 |
+
response = requests.get(health_url, timeout=timeout)
|
| 78 |
+
results["status_code"] = response.status_code
|
| 79 |
+
|
| 80 |
+
if response.status_code == 200:
|
| 81 |
+
results["reachable"] = True
|
| 82 |
+
results["health_check"] = True
|
| 83 |
+
try:
|
| 84 |
+
data = response.json()
|
| 85 |
+
print(f"✓ Health check passed: {data}")
|
| 86 |
+
except:
|
| 87 |
+
print(f"✓ Health check passed (status 200)")
|
| 88 |
+
else:
|
| 89 |
+
results["error"] = f"Health endpoint returned status {response.status_code}"
|
| 90 |
+
print(f"⚠ Health endpoint returned status {response.status_code}")
|
| 91 |
+
except requests.exceptions.ConnectionError:
|
| 92 |
+
results["error"] = "Connection refused - API not reachable"
|
| 93 |
+
print(f"✗ Connection refused - Cannot reach {url}")
|
| 94 |
+
return results
|
| 95 |
+
except requests.exceptions.Timeout:
|
| 96 |
+
results["error"] = f"Connection timeout after {timeout}s"
|
| 97 |
+
print(f"✗ Connection timeout - API not responding")
|
| 98 |
+
return results
|
| 99 |
+
except Exception as e:
|
| 100 |
+
results["error"] = f"Error: {e}"
|
| 101 |
+
print(f"✗ Error: {e}")
|
| 102 |
+
return results
|
| 103 |
+
|
| 104 |
+
# Test 2: Ready endpoint
|
| 105 |
+
try:
|
| 106 |
+
ready_url = f"{url}/ready"
|
| 107 |
+
results["endpoints_tested"].append(ready_url)
|
| 108 |
+
response = requests.get(ready_url, timeout=timeout)
|
| 109 |
+
if response.status_code == 200:
|
| 110 |
+
results["ready_check"] = True
|
| 111 |
+
try:
|
| 112 |
+
data = response.json()
|
| 113 |
+
if data.get("ready", False):
|
| 114 |
+
print(f"✓ API is ready: {data}")
|
| 115 |
+
else:
|
| 116 |
+
print(f"⚠ API not ready yet: {data}")
|
| 117 |
+
except:
|
| 118 |
+
print(f"✓ Ready endpoint accessible")
|
| 119 |
+
else:
|
| 120 |
+
print(f"⚠ Ready endpoint returned status {response.status_code}")
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print(f"⚠ Ready endpoint check failed: {e}")
|
| 123 |
+
|
| 124 |
+
# Test 3: API info endpoint
|
| 125 |
+
try:
|
| 126 |
+
info_url = f"{url}/"
|
| 127 |
+
results["endpoints_tested"].append(info_url)
|
| 128 |
+
response = requests.get(info_url, timeout=timeout)
|
| 129 |
+
if response.status_code == 200:
|
| 130 |
+
results["api_info"] = True
|
| 131 |
+
try:
|
| 132 |
+
data = response.json()
|
| 133 |
+
print(f"✓ API info: {data}")
|
| 134 |
+
except:
|
| 135 |
+
print(f"✓ API root endpoint accessible")
|
| 136 |
+
else:
|
| 137 |
+
print(f"⚠ API root returned status {response.status_code}")
|
| 138 |
+
except Exception as e:
|
| 139 |
+
print(f"⚠ API info check failed: {e}")
|
| 140 |
+
|
| 141 |
+
return results
|
| 142 |
+
|
| 143 |
+
def test_authentication(url: str, email: str, password: str) -> Dict[str, Any]:
|
| 144 |
+
"""
|
| 145 |
+
Test authentication with ZeroGPU API
|
| 146 |
+
|
| 147 |
+
Returns:
|
| 148 |
+
Dictionary with authentication test results
|
| 149 |
+
"""
|
| 150 |
+
results = {
|
| 151 |
+
"authenticated": False,
|
| 152 |
+
"access_token": None,
|
| 153 |
+
"error": None
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
try:
|
| 157 |
+
login_url = f"{url}/login"
|
| 158 |
+
response = requests.post(
|
| 159 |
+
login_url,
|
| 160 |
+
json={"email": email, "password": password},
|
| 161 |
+
timeout=10
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
if response.status_code == 200:
|
| 165 |
+
data = response.json()
|
| 166 |
+
results["authenticated"] = True
|
| 167 |
+
results["access_token"] = "***" if data.get("access_token") else None
|
| 168 |
+
print(f"✓ Authentication successful")
|
| 169 |
+
return results
|
| 170 |
+
else:
|
| 171 |
+
results["error"] = f"Login failed with status {response.status_code}: {response.text[:200]}"
|
| 172 |
+
print(f"✗ Authentication failed: {results['error']}")
|
| 173 |
+
return results
|
| 174 |
+
except Exception as e:
|
| 175 |
+
results["error"] = f"Authentication error: {e}"
|
| 176 |
+
print(f"✗ Authentication error: {e}")
|
| 177 |
+
return results
|
| 178 |
+
|
| 179 |
+
def main():
|
| 180 |
+
parser = argparse.ArgumentParser(
|
| 181 |
+
description="Verify ZeroGPU API URL and connectivity"
|
| 182 |
+
)
|
| 183 |
+
parser.add_argument(
|
| 184 |
+
"url",
|
| 185 |
+
nargs="?",
|
| 186 |
+
help="ZeroGPU API URL (e.g., http://your-pod-ip:8000)"
|
| 187 |
+
)
|
| 188 |
+
parser.add_argument(
|
| 189 |
+
"--email",
|
| 190 |
+
help="Email for authentication test (optional)"
|
| 191 |
+
)
|
| 192 |
+
parser.add_argument(
|
| 193 |
+
"--password",
|
| 194 |
+
help="Password for authentication test (optional)"
|
| 195 |
+
)
|
| 196 |
+
parser.add_argument(
|
| 197 |
+
"--no-validate",
|
| 198 |
+
action="store_true",
|
| 199 |
+
help="Skip URL format validation"
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
args = parser.parse_args()
|
| 203 |
+
|
| 204 |
+
# Get URL from argument or environment
|
| 205 |
+
url = args.url or os.getenv("ZERO_GPU_API_URL", "")
|
| 206 |
+
|
| 207 |
+
if not url:
|
| 208 |
+
print("Error: ZeroGPU API URL not provided")
|
| 209 |
+
print("Usage: python verify_zerogpu_url.py <url>")
|
| 210 |
+
print(" or: Set ZERO_GPU_API_URL environment variable")
|
| 211 |
+
print("\nExample:")
|
| 212 |
+
print(" python verify_zerogpu_url.py http://192.168.1.100:8000")
|
| 213 |
+
sys.exit(1)
|
| 214 |
+
|
| 215 |
+
# Normalize URL
|
| 216 |
+
url = url.rstrip('/')
|
| 217 |
+
|
| 218 |
+
print("=" * 60)
|
| 219 |
+
print("ZeroGPU API URL Verification")
|
| 220 |
+
print("=" * 60)
|
| 221 |
+
print(f"URL: {url}\n")
|
| 222 |
+
|
| 223 |
+
# Step 1: Validate URL format
|
| 224 |
+
if not args.no_validate:
|
| 225 |
+
print("Step 1: Validating URL format...")
|
| 226 |
+
is_valid, error = validate_url_format(url)
|
| 227 |
+
if not is_valid:
|
| 228 |
+
print(f"✗ URL validation failed: {error}")
|
| 229 |
+
print("\nCorrect format examples:")
|
| 230 |
+
print(" https://bm9njt1ypzvuqw-8000.proxy.runpod.net")
|
| 231 |
+
print(" https://<pod-id>-8000.proxy.runpod.net")
|
| 232 |
+
print(" http://<ip-address>:8000 (if using direct IP)")
|
| 233 |
+
sys.exit(1)
|
| 234 |
+
print("✓ URL format is valid")
|
| 235 |
+
else:
|
| 236 |
+
print("Step 1: Skipping URL format validation")
|
| 237 |
+
|
| 238 |
+
# Step 2: Test connectivity
|
| 239 |
+
print("\nStep 2: Testing connectivity...")
|
| 240 |
+
connectivity_results = test_connectivity(url)
|
| 241 |
+
|
| 242 |
+
if not connectivity_results["reachable"]:
|
| 243 |
+
print("\n✗ Connectivity test failed")
|
| 244 |
+
print(f"Error: {connectivity_results.get('error', 'Unknown error')}")
|
| 245 |
+
print("\nTroubleshooting:")
|
| 246 |
+
print("1. Verify the Runpod pod is running")
|
| 247 |
+
print("2. Check the pod IP address is correct")
|
| 248 |
+
print("3. Ensure port 8000 is exposed")
|
| 249 |
+
print("4. Check firewall/network rules")
|
| 250 |
+
sys.exit(1)
|
| 251 |
+
|
| 252 |
+
# Step 3: Test authentication (if credentials provided)
|
| 253 |
+
if args.email and args.password:
|
| 254 |
+
print("\nStep 3: Testing authentication...")
|
| 255 |
+
auth_results = test_authentication(url, args.email, args.password)
|
| 256 |
+
if not auth_results["authenticated"]:
|
| 257 |
+
print(f"\n⚠ Authentication test failed: {auth_results.get('error', 'Unknown error')}")
|
| 258 |
+
print("Note: Connectivity is working, but authentication failed.")
|
| 259 |
+
print("Please verify your credentials.")
|
| 260 |
+
else:
|
| 261 |
+
print("\nStep 3: Skipping authentication test (no credentials provided)")
|
| 262 |
+
print("To test authentication, use: --email <email> --password <password>")
|
| 263 |
+
|
| 264 |
+
# Summary
|
| 265 |
+
print("\n" + "=" * 60)
|
| 266 |
+
print("Verification Summary")
|
| 267 |
+
print("=" * 60)
|
| 268 |
+
print(f"URL: {url}")
|
| 269 |
+
print(f"Reachable: {'✓ Yes' if connectivity_results['reachable'] else '✗ No'}")
|
| 270 |
+
print(f"Health Check: {'✓ Passed' if connectivity_results['health_check'] else '✗ Failed'}")
|
| 271 |
+
print(f"Ready Check: {'✓ Passed' if connectivity_results['ready_check'] else '⚠ Not tested/Failed'}")
|
| 272 |
+
print(f"API Info: {'✓ Accessible' if connectivity_results['api_info'] else '⚠ Not tested/Failed'}")
|
| 273 |
+
|
| 274 |
+
if connectivity_results["reachable"]:
|
| 275 |
+
print("\n✓ ZeroGPU API URL is valid and working!")
|
| 276 |
+
print("\n✓ ZeroGPU API URL is valid and working!")
|
| 277 |
+
print("\nYou can use this URL in your environment:")
|
| 278 |
+
print(f" ZERO_GPU_API_URL={url}")
|
| 279 |
+
print("\nFor Hugging Face Spaces, add this as a Repository Secret:")
|
| 280 |
+
print(f" Name: ZERO_GPU_API_URL")
|
| 281 |
+
print(f" Value: {url}")
|
| 282 |
+
else:
|
| 283 |
+
print("\n✗ ZeroGPU API URL is not working")
|
| 284 |
+
sys.exit(1)
|
| 285 |
+
|
| 286 |
+
if __name__ == "__main__":
|
| 287 |
+
main()
|
| 288 |
+
|
|
@@ -6,8 +6,10 @@ Provides authentication and API access to ZeroGPU Chat API
|
|
| 6 |
import requests
|
| 7 |
import time
|
| 8 |
import logging
|
|
|
|
| 9 |
from typing import Optional, List, Dict, Any
|
| 10 |
from datetime import datetime
|
|
|
|
| 11 |
|
| 12 |
logger = logging.getLogger(__name__)
|
| 13 |
|
|
@@ -15,18 +17,20 @@ logger = logging.getLogger(__name__)
|
|
| 15 |
class ZeroGPUChatClient:
|
| 16 |
"""Client for ZeroGPU Chat API with automatic token refresh"""
|
| 17 |
|
| 18 |
-
def __init__(self, base_url: str, email: str, password: str, access_token: str = None, refresh_token: str = None):
|
| 19 |
"""
|
| 20 |
Initialize ZeroGPU API client
|
| 21 |
|
| 22 |
Args:
|
| 23 |
-
base_url: Base URL of ZeroGPU API (e.g., "http://your-pod-ip:8000")
|
| 24 |
email: User email for authentication
|
| 25 |
password: User password for authentication
|
| 26 |
access_token: Optional pre-existing access token
|
| 27 |
refresh_token: Optional pre-existing refresh token
|
|
|
|
| 28 |
"""
|
| 29 |
-
|
|
|
|
| 30 |
self.email = email
|
| 31 |
self.password = password
|
| 32 |
self.access_token = access_token
|
|
@@ -42,6 +46,95 @@ class ZeroGPUChatClient:
|
|
| 42 |
else:
|
| 43 |
self.login(email, password)
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
def login(self, email: str, password: str):
|
| 46 |
"""Login and get authentication tokens"""
|
| 47 |
try:
|
|
@@ -221,7 +314,55 @@ class ZeroGPUChatClient:
|
|
| 221 |
"""Check if API is healthy"""
|
| 222 |
try:
|
| 223 |
response = requests.get(f"{self.base_url}/health", timeout=5)
|
| 224 |
-
|
| 225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
|
|
|
| 6 |
import requests
|
| 7 |
import time
|
| 8 |
import logging
|
| 9 |
+
import re
|
| 10 |
from typing import Optional, List, Dict, Any
|
| 11 |
from datetime import datetime
|
| 12 |
+
from urllib.parse import urlparse
|
| 13 |
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
|
|
|
| 17 |
class ZeroGPUChatClient:
|
| 18 |
"""Client for ZeroGPU Chat API with automatic token refresh"""
|
| 19 |
|
| 20 |
+
def __init__(self, base_url: str, email: str, password: str, access_token: str = None, refresh_token: str = None, validate_url: bool = True):
|
| 21 |
"""
|
| 22 |
Initialize ZeroGPU API client
|
| 23 |
|
| 24 |
Args:
|
| 25 |
+
base_url: Base URL of ZeroGPU API (e.g., "http://your-pod-ip:8000" or "https://your-domain.com")
|
| 26 |
email: User email for authentication
|
| 27 |
password: User password for authentication
|
| 28 |
access_token: Optional pre-existing access token
|
| 29 |
refresh_token: Optional pre-existing refresh token
|
| 30 |
+
validate_url: Whether to validate URL format and connectivity (default: True)
|
| 31 |
"""
|
| 32 |
+
# Validate and normalize URL
|
| 33 |
+
self.base_url = self._validate_and_normalize_url(base_url, validate_url)
|
| 34 |
self.email = email
|
| 35 |
self.password = password
|
| 36 |
self.access_token = access_token
|
|
|
|
| 46 |
else:
|
| 47 |
self.login(email, password)
|
| 48 |
|
| 49 |
+
def _validate_and_normalize_url(self, url: str, validate: bool = True) -> str:
|
| 50 |
+
"""
|
| 51 |
+
Validate and normalize the ZeroGPU API URL
|
| 52 |
+
|
| 53 |
+
Args:
|
| 54 |
+
url: URL to validate
|
| 55 |
+
validate: Whether to perform connectivity check
|
| 56 |
+
|
| 57 |
+
Returns:
|
| 58 |
+
Normalized URL string
|
| 59 |
+
|
| 60 |
+
Raises:
|
| 61 |
+
ValueError: If URL format is invalid
|
| 62 |
+
ConnectionError: If URL is not reachable (when validate=True)
|
| 63 |
+
"""
|
| 64 |
+
# Remove trailing slash
|
| 65 |
+
url = url.rstrip('/')
|
| 66 |
+
|
| 67 |
+
# Check for placeholder values
|
| 68 |
+
if 'your-pod-ip' in url.lower() or (url.lower() == 'http://localhost:8000' and validate):
|
| 69 |
+
raise ValueError(
|
| 70 |
+
f"Invalid ZeroGPU API URL: '{url}'. "
|
| 71 |
+
"Please set ZERO_GPU_API_URL to your actual Runpod proxy URL. "
|
| 72 |
+
"Format: https://<pod-id>-8000.proxy.runpod.net"
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
# Parse URL to validate format
|
| 76 |
+
try:
|
| 77 |
+
parsed = urlparse(url)
|
| 78 |
+
if not parsed.scheme:
|
| 79 |
+
raise ValueError(f"URL missing scheme (http:// or https://): {url}")
|
| 80 |
+
if not parsed.netloc:
|
| 81 |
+
raise ValueError(f"URL missing host/domain: {url}")
|
| 82 |
+
if parsed.scheme not in ['http', 'https']:
|
| 83 |
+
raise ValueError(f"URL must use http:// or https://, got: {parsed.scheme}")
|
| 84 |
+
except Exception as e:
|
| 85 |
+
raise ValueError(f"Invalid URL format: {url}. Error: {e}")
|
| 86 |
+
|
| 87 |
+
# Validate connectivity if requested
|
| 88 |
+
if validate:
|
| 89 |
+
if not self._check_url_connectivity(url):
|
| 90 |
+
raise ConnectionError(
|
| 91 |
+
f"Cannot connect to ZeroGPU API at {url}. "
|
| 92 |
+
"Please verify:\n"
|
| 93 |
+
"1. The URL is correct (check for typos)\n"
|
| 94 |
+
"2. The Runpod pod is running and accessible\n"
|
| 95 |
+
"3. Network connectivity from this host to the pod\n"
|
| 96 |
+
"4. Firewall rules allow connections to port 8000"
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
return url
|
| 100 |
+
|
| 101 |
+
def _check_url_connectivity(self, url: str, timeout: int = 5) -> bool:
|
| 102 |
+
"""
|
| 103 |
+
Check if the URL is reachable
|
| 104 |
+
|
| 105 |
+
Args:
|
| 106 |
+
url: URL to check
|
| 107 |
+
timeout: Connection timeout in seconds
|
| 108 |
+
|
| 109 |
+
Returns:
|
| 110 |
+
True if reachable, False otherwise
|
| 111 |
+
"""
|
| 112 |
+
try:
|
| 113 |
+
# Try to reach the health endpoint
|
| 114 |
+
health_url = f"{url}/health"
|
| 115 |
+
response = requests.get(health_url, timeout=timeout)
|
| 116 |
+
|
| 117 |
+
# Accept 200 (OK) or 404 (endpoint might not exist but server is reachable)
|
| 118 |
+
if response.status_code in [200, 404]:
|
| 119 |
+
logger.info(f"✓ ZeroGPU API URL is reachable: {url}")
|
| 120 |
+
return True
|
| 121 |
+
else:
|
| 122 |
+
logger.warning(f"ZeroGPU API returned status {response.status_code} for {url}")
|
| 123 |
+
return False
|
| 124 |
+
|
| 125 |
+
except requests.exceptions.ConnectionError:
|
| 126 |
+
logger.error(f"✗ Cannot connect to ZeroGPU API at {url} - Connection refused")
|
| 127 |
+
return False
|
| 128 |
+
except requests.exceptions.Timeout:
|
| 129 |
+
logger.error(f"✗ Connection to ZeroGPU API at {url} timed out")
|
| 130 |
+
return False
|
| 131 |
+
except requests.exceptions.RequestException as e:
|
| 132 |
+
logger.error(f"✗ Error checking ZeroGPU API URL {url}: {e}")
|
| 133 |
+
return False
|
| 134 |
+
except Exception as e:
|
| 135 |
+
logger.error(f"✗ Unexpected error checking URL {url}: {e}")
|
| 136 |
+
return False
|
| 137 |
+
|
| 138 |
def login(self, email: str, password: str):
|
| 139 |
"""Login and get authentication tokens"""
|
| 140 |
try:
|
|
|
|
| 314 |
"""Check if API is healthy"""
|
| 315 |
try:
|
| 316 |
response = requests.get(f"{self.base_url}/health", timeout=5)
|
| 317 |
+
if response.status_code == 200:
|
| 318 |
+
logger.debug(f"Health check passed for {self.base_url}")
|
| 319 |
+
return True
|
| 320 |
+
else:
|
| 321 |
+
logger.warning(f"Health check returned status {response.status_code} for {self.base_url}")
|
| 322 |
+
return False
|
| 323 |
+
except requests.exceptions.RequestException as e:
|
| 324 |
+
logger.warning(f"Health check failed for {self.base_url}: {e}")
|
| 325 |
return False
|
| 326 |
+
|
| 327 |
+
def verify_url(self) -> Dict[str, Any]:
|
| 328 |
+
"""
|
| 329 |
+
Comprehensive URL verification
|
| 330 |
+
|
| 331 |
+
Returns:
|
| 332 |
+
Dictionary with verification results
|
| 333 |
+
"""
|
| 334 |
+
results = {
|
| 335 |
+
"url": self.base_url,
|
| 336 |
+
"format_valid": False,
|
| 337 |
+
"reachable": False,
|
| 338 |
+
"health_check": False,
|
| 339 |
+
"ready_check": False,
|
| 340 |
+
"error": None
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
# Format validation
|
| 344 |
+
try:
|
| 345 |
+
parsed = urlparse(self.base_url)
|
| 346 |
+
if parsed.scheme and parsed.netloc:
|
| 347 |
+
results["format_valid"] = True
|
| 348 |
+
except:
|
| 349 |
+
results["error"] = "Invalid URL format"
|
| 350 |
+
return results
|
| 351 |
+
|
| 352 |
+
# Connectivity check
|
| 353 |
+
results["reachable"] = self._check_url_connectivity(self.base_url, timeout=5)
|
| 354 |
+
|
| 355 |
+
# Health check
|
| 356 |
+
results["health_check"] = self.health_check()
|
| 357 |
+
|
| 358 |
+
# Ready check
|
| 359 |
+
try:
|
| 360 |
+
response = requests.get(f"{self.base_url}/ready", timeout=5)
|
| 361 |
+
if response.status_code == 200:
|
| 362 |
+
data = response.json()
|
| 363 |
+
results["ready_check"] = data.get("ready", False)
|
| 364 |
+
except:
|
| 365 |
+
pass
|
| 366 |
+
|
| 367 |
+
return results
|
| 368 |
|