- Replace form_urlencoded with RFC 3986 compliant URI encoding - Implement aws_uri_encode() matching AWS SigV4 spec exactly - Unreserved chars (A-Z,a-z,0-9,-,_,.,~) not encoded - All other chars percent-encoded with uppercase hex - Preserve slashes in paths, encode in query params - Normalize empty paths to '/' per AWS spec - Fix test expectations (body hash, HMAC values) - Add comprehensive SigV4 signature determinism test This fixes the canonicalization mismatch that caused signature validation failures in T047. Auth can now be enabled for production. Refs: T058.S1
363 lines
9 KiB
Markdown
363 lines
9 KiB
Markdown
# PhotonCloud REST API Patterns
|
|
|
|
**Status:** Draft (T050.S1)
|
|
**Created:** 2025-12-12
|
|
**Author:** PeerA
|
|
|
|
## Overview
|
|
|
|
This document defines consistent REST API patterns for all PhotonCloud services.
|
|
Goal: curl/シェルスクリプト/組み込み環境で簡単に使える HTTP API.
|
|
|
|
## URL Structure
|
|
|
|
```
|
|
{scheme}://{host}:{port}/api/v1/{resource}[/{id}][/{action}]
|
|
```
|
|
|
|
### Examples
|
|
```
|
|
GET /api/v1/kv/mykey # Get key
|
|
PUT /api/v1/kv/mykey # Put key
|
|
DELETE /api/v1/kv/mykey # Delete key
|
|
GET /api/v1/vms # List VMs
|
|
POST /api/v1/vms # Create VM
|
|
GET /api/v1/vms/vm-123 # Get VM
|
|
DELETE /api/v1/vms/vm-123 # Delete VM
|
|
POST /api/v1/vms/vm-123/start # Start VM (action)
|
|
POST /api/v1/vms/vm-123/stop # Stop VM (action)
|
|
```
|
|
|
|
## HTTP Methods
|
|
|
|
| Method | Usage | Idempotent |
|
|
|--------|-------|------------|
|
|
| GET | Read resource(s) | Yes |
|
|
| POST | Create resource or execute action | No |
|
|
| PUT | Create or replace resource | Yes |
|
|
| DELETE | Delete resource | Yes |
|
|
| PATCH | Partial update (optional) | No |
|
|
|
|
## Request Format
|
|
|
|
### Content-Type
|
|
```
|
|
Content-Type: application/json
|
|
```
|
|
|
|
### Authentication
|
|
```
|
|
Authorization: Bearer <jwt-token>
|
|
```
|
|
|
|
Token obtained from IAM:
|
|
```bash
|
|
# Get token
|
|
curl -X POST http://iam:8081/api/v1/auth/token \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username": "admin", "password": "secret"}'
|
|
|
|
# Response
|
|
{"token": "eyJ..."}
|
|
|
|
# Use token
|
|
curl http://chainfire:8082/api/v1/kv/mykey \
|
|
-H "Authorization: Bearer eyJ..."
|
|
```
|
|
|
|
### Request Body (POST/PUT)
|
|
```json
|
|
{
|
|
"field1": "value1",
|
|
"field2": 123
|
|
}
|
|
```
|
|
|
|
## Response Format
|
|
|
|
### Success Response
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
// Resource data
|
|
},
|
|
"meta": {
|
|
"request_id": "req-abc123",
|
|
"timestamp": "2025-12-12T01:40:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
### List Response
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{ "id": "item-1", ... },
|
|
{ "id": "item-2", ... }
|
|
],
|
|
"meta": {
|
|
"total": 42,
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"request_id": "req-abc123"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error Response
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "NOT_FOUND",
|
|
"message": "Resource not found",
|
|
"details": {
|
|
"resource": "vm",
|
|
"id": "vm-123"
|
|
}
|
|
},
|
|
"meta": {
|
|
"request_id": "req-abc123",
|
|
"timestamp": "2025-12-12T01:40:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error Codes
|
|
|
|
| HTTP Status | Error Code | Description |
|
|
|-------------|------------|-------------|
|
|
| 400 | BAD_REQUEST | Invalid request format |
|
|
| 401 | UNAUTHORIZED | Missing or invalid token |
|
|
| 403 | FORBIDDEN | Insufficient permissions |
|
|
| 404 | NOT_FOUND | Resource not found |
|
|
| 409 | CONFLICT | Resource already exists |
|
|
| 422 | VALIDATION_ERROR | Request validation failed |
|
|
| 429 | RATE_LIMITED | Too many requests |
|
|
| 500 | INTERNAL_ERROR | Server error |
|
|
| 503 | SERVICE_UNAVAILABLE | Service temporarily unavailable |
|
|
|
|
## Pagination
|
|
|
|
### Request
|
|
```
|
|
GET /api/v1/vms?limit=20&offset=40
|
|
```
|
|
|
|
### Parameters
|
|
| Param | Type | Default | Max | Description |
|
|
|-------|------|---------|-----|-------------|
|
|
| limit | int | 20 | 100 | Items per page |
|
|
| offset | int | 0 | - | Skip N items |
|
|
|
|
## Filtering
|
|
|
|
### Query Parameters
|
|
```
|
|
GET /api/v1/vms?status=running&project_id=proj-123
|
|
```
|
|
|
|
### Prefix Search (KV)
|
|
```
|
|
GET /api/v1/kv?prefix=config/
|
|
```
|
|
|
|
## Port Convention
|
|
|
|
| Service | gRPC Port | HTTP Port |
|
|
|---------|-----------|-----------|
|
|
| ChainFire | 50051 | 8081 |
|
|
| FlareDB | 50052 | 8082 |
|
|
| IAM | 50053 | 8083 |
|
|
| PlasmaVMC | 50054 | 8084 |
|
|
| k8shost | 50055 | 8085 |
|
|
| LightningSTOR | 50056 | 8086 |
|
|
| CreditService | 50057 | 8087 |
|
|
| PrismNET | 50058 | 8088 |
|
|
| NightLight | 50059 | 8089 |
|
|
| FiberLB | 50060 | 8090 |
|
|
| FlashDNS | 50061 | 8091 |
|
|
|
|
## Service-Specific Endpoints
|
|
|
|
### ChainFire (KV Store)
|
|
```
|
|
GET /api/v1/kv/{key} # Get value
|
|
PUT /api/v1/kv/{key} # Put value {"value": "..."}
|
|
DELETE /api/v1/kv/{key} # Delete key
|
|
GET /api/v1/kv?prefix={prefix} # Range scan
|
|
GET /api/v1/cluster/status # Cluster health
|
|
POST /api/v1/cluster/members # Add member
|
|
```
|
|
|
|
### FlareDB (Database)
|
|
```
|
|
POST /api/v1/sql # Execute SQL {"query": "SELECT ..."}
|
|
GET /api/v1/tables # List tables
|
|
GET /api/v1/kv/{key} # KV get
|
|
PUT /api/v1/kv/{key} # KV put
|
|
GET /api/v1/scan?start={}&end={} # Range scan
|
|
```
|
|
|
|
### IAM (Authentication)
|
|
```
|
|
POST /api/v1/auth/token # Get token
|
|
POST /api/v1/auth/verify # Verify token
|
|
GET /api/v1/users # List users
|
|
POST /api/v1/users # Create user
|
|
GET /api/v1/users/{id} # Get user
|
|
DELETE /api/v1/users/{id} # Delete user
|
|
GET /api/v1/projects # List projects
|
|
POST /api/v1/projects # Create project
|
|
```
|
|
|
|
### PlasmaVMC (VM Management)
|
|
```
|
|
GET /api/v1/vms # List VMs
|
|
POST /api/v1/vms # Create VM
|
|
GET /api/v1/vms/{id} # Get VM
|
|
DELETE /api/v1/vms/{id} # Delete VM
|
|
POST /api/v1/vms/{id}/start # Start VM
|
|
POST /api/v1/vms/{id}/stop # Stop VM
|
|
POST /api/v1/vms/{id}/reboot # Reboot VM
|
|
GET /api/v1/vms/{id}/console # Get console URL
|
|
```
|
|
|
|
### k8shost (Kubernetes)
|
|
```
|
|
GET /api/v1/pods # List pods
|
|
POST /api/v1/pods # Create pod
|
|
GET /api/v1/pods/{name} # Get pod
|
|
DELETE /api/v1/pods/{name} # Delete pod
|
|
GET /api/v1/services # List services
|
|
POST /api/v1/services # Create service
|
|
GET /api/v1/nodes # List nodes
|
|
```
|
|
|
|
### CreditService (Billing)
|
|
```
|
|
GET /api/v1/wallets/{project_id} # Get wallet balance
|
|
POST /api/v1/wallets/{project_id}/reserve # Reserve credits
|
|
POST /api/v1/wallets/{project_id}/commit # Commit reservation
|
|
POST /api/v1/wallets/{project_id}/release # Release reservation
|
|
GET /api/v1/quotas/{project_id} # Get quotas
|
|
PUT /api/v1/quotas/{project_id} # Set quotas
|
|
```
|
|
|
|
### NightLight (Metrics) - Already HTTP
|
|
```
|
|
POST /api/v1/write # Push metrics (Prometheus remote write)
|
|
GET /api/v1/query?query={promql} # Instant query
|
|
GET /api/v1/query_range?query={}&start={}&end={}&step={} # Range query
|
|
GET /api/v1/series?match[]={}&start={}&end={} # Series metadata
|
|
GET /api/v1/labels # List labels
|
|
```
|
|
|
|
### LightningSTOR (S3) - Already HTTP
|
|
```
|
|
# S3-compatible (different port/path)
|
|
PUT /{bucket} # Create bucket
|
|
DELETE /{bucket} # Delete bucket
|
|
GET / # List buckets
|
|
PUT /{bucket}/{key} # Put object
|
|
GET /{bucket}/{key} # Get object
|
|
DELETE /{bucket}/{key} # Delete object
|
|
GET /{bucket}?list-type=2 # List objects
|
|
```
|
|
|
|
## curl Examples
|
|
|
|
### ChainFire KV
|
|
```bash
|
|
# Put
|
|
curl -X PUT http://localhost:8081/api/v1/kv/mykey \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-d '{"value": "hello world"}'
|
|
|
|
# Get
|
|
curl http://localhost:8081/api/v1/kv/mykey \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
|
|
# Delete
|
|
curl -X DELETE http://localhost:8081/api/v1/kv/mykey \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
### PlasmaVMC
|
|
```bash
|
|
# Create VM
|
|
curl -X POST http://localhost:8084/api/v1/vms \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-d '{
|
|
"name": "my-vm",
|
|
"cpu_cores": 2,
|
|
"memory_mb": 4096,
|
|
"disk_gb": 20,
|
|
"image": "ubuntu-22.04"
|
|
}'
|
|
|
|
# List VMs
|
|
curl http://localhost:8084/api/v1/vms \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
|
|
# Start VM
|
|
curl -X POST http://localhost:8084/api/v1/vms/vm-123/start \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
### FlareDB SQL
|
|
```bash
|
|
# Execute query
|
|
curl -X POST http://localhost:8082/api/v1/sql \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-d '{"query": "SELECT * FROM users WHERE id = 1"}'
|
|
```
|
|
|
|
## Implementation Notes
|
|
|
|
### Rust Framework
|
|
Use `axum` (already in most services):
|
|
```rust
|
|
use axum::{
|
|
routing::{get, post, put, delete},
|
|
Router, Json, extract::Path,
|
|
};
|
|
|
|
let app = Router::new()
|
|
.route("/api/v1/kv/:key", get(get_kv).put(put_kv).delete(delete_kv))
|
|
.route("/api/v1/cluster/status", get(cluster_status));
|
|
```
|
|
|
|
### Run Alongside gRPC
|
|
```rust
|
|
// Start both servers
|
|
tokio::select! {
|
|
_ = grpc_server.serve(grpc_addr) => {},
|
|
_ = axum::Server::bind(&http_addr).serve(app.into_make_service()) => {},
|
|
}
|
|
```
|
|
|
|
### Error Mapping
|
|
```rust
|
|
impl From<ServiceError> for HttpError {
|
|
fn from(e: ServiceError) -> Self {
|
|
match e {
|
|
ServiceError::NotFound(_) => HttpError::not_found(e.to_string()),
|
|
ServiceError::AlreadyExists(_) => HttpError::conflict(e.to_string()),
|
|
ServiceError::InvalidArgument(_) => HttpError::bad_request(e.to_string()),
|
|
_ => HttpError::internal(e.to_string()),
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## References
|
|
|
|
- T050 Task: docs/por/T050-rest-api/task.yaml
|
|
- PROJECT.md: 統一API/仕様
|
|
- Existing HTTP: NightLight (metrics), LightningSTOR (S3)
|