The DBaaS controller includes a lightweight HTTP REST gateway (internal/gateway/gateway.go) that exposes DBInstance CRUD operations. It runs as a goroutine alongside the controller manager and uses the controller-runtime client.Client to read/write CRDs directly.
Default address: :8080 (configurable via --gateway-address)
Note: The gateway currently has no authentication, TLS, or rate limiting. Place it behind an ingress or API gateway with your auth layer for production use.
| Method | Path | Description |
|---|---|---|
GET |
/healthz |
Health check |
GET |
/dbinstances |
List all DBInstances |
POST |
/dbinstances |
Create a new DBInstance |
GET |
/dbinstances/{name} |
Get a single DBInstance |
DELETE |
/dbinstances/{name} |
Delete a DBInstance |
POST |
/dbinstances/{name}/stop |
Stop a running instance |
POST |
/dbinstances/{name}/start |
Start a stopped instance |
All responses are Content-Type: application/json.
GET /healthz
Response: 200 OK
{"status": "ok"}GET /dbinstances
Returns the full DBInstanceList CRD object.
Response: 200 OK
{
"apiVersion": "dbaas.wso2.com/v1alpha1",
"kind": "DBInstanceList",
"metadata": {},
"items": [
{
"metadata": { "name": "orders-prod" },
"spec": {
"dbInstanceClass": "db.m5.large",
"allocatedStorage": 100,
"dbName": "orders"
},
"status": {
"phase": "available",
"provisioningPhase": "Available",
"endpoint": {
"address": "10.142.87.5",
"port": 5432,
"jdbcUrl": "jdbc:postgresql://10.142.87.5:5432/orders?ssl=true&sslmode=verify-full"
}
}
}
]
}POST /dbinstances
Content-Type: application/json
The request body is a DBInstance CRD object. apiVersion and kind are auto-filled if omitted. metadata.name is required.
Request:
{
"metadata": {
"name": "orders-prod"
},
"spec": {
"dbInstanceClass": "db.m5.large",
"allocatedStorage": 100,
"dbName": "orders",
"masterUsername": "orders_admin",
"manageMasterUserPassword": true,
"dbSubnetGroupName": "10.50.0.0/24",
"backupRetentionPeriod": 7,
"deletionProtection": true
}
}Response: 202 Accepted
{
"apiVersion": "dbaas.wso2.com/v1alpha1",
"kind": "DBInstance",
"metadata": {
"name": "orders-prod",
"creationTimestamp": "2026-03-30T10:00:00Z"
},
"spec": { "..." : "..." },
"status": {}
}Errors:
400— missingmetadata.nameor invalid JSON409— instance with that name already exists
GET /dbinstances/{name}
Response: 200 OK
{
"apiVersion": "dbaas.wso2.com/v1alpha1",
"kind": "DBInstance",
"metadata": { "name": "orders-prod" },
"spec": {
"dbInstanceClass": "db.m5.large",
"allocatedStorage": 100,
"dbName": "orders",
"port": 5432
},
"status": {
"phase": "available",
"provisioningPhase": "Available",
"endpoint": {
"address": "10.142.87.5",
"port": 5432,
"jdbcUrl": "jdbc:postgresql://10.142.87.5:5432/orders?ssl=true&sslmode=verify-full"
},
"masterUserSecret": {
"name": "pg-orders-prod-credentials",
"status": "active"
},
"resources": {
"namespace": "dbaas-orders-prod",
"vpcName": "dbaas-orders-prod-vpc",
"subnetName": "dbaas-orders-prod-subnet",
"vmName": "pg-orders-prod"
},
"grafanaUrl": "https://grafana.monitoring.svc/d/dbaas-orders-prod/postgresql-orders-prod",
"prometheusTarget": "pg-orders-prod-metrics.dbaas-orders-prod.svc:9187",
"message": "Database instance is available"
}
}Errors:
404— instance not found
DELETE /dbinstances/{name}
Triggers the controller's finalizer-based teardown: VM, DataVolume, network, secrets, monitoring, and namespace are all cleaned up.
Response: 202 Accepted
{
"status": "deletion requested",
"name": "orders-prod"
}Errors:
404— instance not found
Note: If
spec.deletionProtectionistrue, the controller will reject the deletion and setstatus.messageto explain why. Disable it first via kubectl:kubectl patch dbi orders-prod --type merge -p '{"spec":{"deletionProtection":false}}'
POST /dbinstances/{name}/stop
Sets spec.running = false. The controller shuts down the KubeVirt VM while preserving storage.
Response: 202 Accepted — returns the updated DBInstance object.
The instance transitions through phases:
status.phase→"stopping"status.phase→"stopped"
Errors:
404— instance not found
POST /dbinstances/{name}/start
Sets spec.running = true. The controller boots the KubeVirt VM.
Response: 202 Accepted — returns the updated DBInstance object.
The instance transitions through phases:
status.phase→"starting"status.phase→"available"
Errors:
404— instance not found
These are the possible values for status.phase (RDS-compatible lowercase strings):
| Phase | Meaning |
|---|---|
creating |
Initial provisioning in progress |
available |
Database is ready for connections |
stopping |
VM is shutting down |
stopped |
VM is off, storage preserved |
starting |
VM is booting |
modifying |
Resize or config change in progress |
deleting |
Teardown in progress |
failed |
An error occurred (see status.message) |
status.provisioningPhase tracks the internal reconciler step (PascalCase):
| Phase | What completed |
|---|---|
Pending |
Initial state |
NamespaceCreated |
dbaas-{name} namespace created |
NetworkProvisioned |
Kube-OVN VPC + Subnet + NAD created |
StorageProvisioned |
CDI DataVolume created |
VMCreated |
KubeVirt VM + credentials Secret created |
WaitingForCloudInit |
VM running, PostgreSQL initializing |
DatabaseReady |
PostgreSQL accepting connections |
MonitoringDeployed |
ServiceMonitor + metrics Service created |
Available |
Fully reconciled |
Failed |
Error (retries every 30s) |
GATEWAY=http://localhost:8080
# Create
curl -s -X POST "$GATEWAY/dbinstances" \
-H "Content-Type: application/json" \
-d '{
"metadata": {"name": "mydb"},
"spec": {
"dbInstanceClass": "db.t3.medium",
"allocatedStorage": 50,
"dbName": "myapp",
"dbSubnetGroupName": "10.50.0.0/24"
}
}' | jq .
# Poll status
curl -s "$GATEWAY/dbinstances/mydb" | jq '.status.phase, .status.provisioningPhase'
# Stop
curl -s -X POST "$GATEWAY/dbinstances/mydb/stop" | jq .status.phase
# Start
curl -s -X POST "$GATEWAY/dbinstances/mydb/start" | jq .status.phase
# Delete
curl -s -X DELETE "$GATEWAY/dbinstances/mydb" | jq .