Your First Request
Example API call with curl or HTTP client
This page walks you through making your first authenticated HTTP request to the Trilio Site Recovery API. You will authenticate against Keystone, obtain a token, and call the protector-api to list your Protection Groups — confirming that the service is reachable, your credentials are valid, and the API version negotiation is working correctly. Every subsequent workflow (failover, failback, test failover) builds on exactly these mechanics, so getting this baseline right is the essential first step.
Before you begin, make sure you have the following in place:
- Two OpenStack deployments ≥ Victoria, each with Nova, Cinder, Keystone, and Neutron
- protector-api running on port 8788 on at least one site — confirm with
curl http://<controller-ip>:8788/healthz - A valid OpenStack user account on the primary site with at least member-level access to a project
- A Keystone endpoint on port 5000 accessible from your client machine
curl≥ 7.64 (or any HTTP client that supports setting custom headers) — examples below usecurlpython-openstackclientinstalled if you want to use theopenstack drCLI instead of raw HTTPjq(optional, but used in examples to pretty-print JSON responses)- Network connectivity from your client to both Keystone (port 5000) and protector-api (port 8788)
The openstack dr CLI plugin ships as part of the openstack-protector Python package. Install it into the same environment as python-openstackclient.
Step 1 — Install the CLI plugin
pip install -e /path/to/openstack-protector
Step 2 — Verify the plugin is loaded
openstack --help | grep "^ dr"
You should see the dr command group listed. If nothing appears, the package entry point was not registered — re-run pip install -e . from the repository root and confirm there are no import errors.
Step 3 — Source your OpenStack credentials
Export standard OpenStack environment variables for the primary site, or source your RC file:
source cluster1-openrc.sh
Step 4 — Verify the Protector service endpoint is in the catalog
openstack catalog show protector
The output should show a public endpoint on port 8788. If the service is not in the catalog yet, ask your operator to register it, or set the endpoint manually in your environment:
export OS_PROTECTOR_API_VERSION=1
All requests to the protector-api require two key HTTP headers.
Authentication header
X-Auth-Token: <keystone-token>
Obtain a token from Keystone on port 5000 (not from protector-api — the service has no /v1/auth/tokens endpoint). See the Usage section for the exact curl command.
API microversion header
OpenStack-API-Version: protector 1.0
or
OpenStack-API-Version: protector 1.1
The maximum supported microversion is 1.1. Every request must carry one of these two values. A missing or unrecognised value returns HTTP 400 Bad Request — not 406. Use 1.1 unless you have a specific reason to pin to 1.0.
Base URL shape
All versioned endpoints live under /v1/:
http://<controller>:8788/v1/<tenant_id>/<resource>
Substitute <tenant_id> with your OpenStack project UUID. The only unversioned endpoint is the health check:
http://<controller>:8788/healthz
Key protector.conf options (for operators)
| Section | Option | Default | Purpose |
|---|---|---|---|
[api] | bind_host | 0.0.0.0 | Address the API WSGI server binds to |
[api] | bind_port | 8788 | Port the API listens on |
[api] | workers | 4 | Number of API worker processes |
[database] | connection | — | MariaDB/MySQL connection string |
[DEFAULT] | storage_driver | pure_flasharray | Storage backend driver (pure_flasharray, drbd, or mock) |
Configuration is managed entirely through protector.conf files; there is no /v1/config endpoint.
The two most common ways to interact with the API are raw HTTP with curl and the openstack dr CLI plugin. Both are shown below.
Step 1 — Obtain a Keystone token
Authenticate directly against Keystone on port 5000. The protector-api does not issue tokens.
TOKEN=$(openstack token issue -f value -c id)
echo $TOKEN
Or with curl:
TOKEN=$(curl -s -X POST http://<keystone-host>:5000/v3/auth/tokens \
-H "Content-Type: application/json" \
-d '{
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "<username>",
"domain": {"name": "Default"},
"password": "<password>"
}
}
},
"scope": {
"project": {
"name": "<project-name>",
"domain": {"name": "Default"}
}
}
}
}' \
-i | grep -i '^x-subject-token' | awk '{print $2}' | tr -d '\r')
Store your project UUID — you will need it in every API path:
TENANT_ID=$(openstack project show <project-name> -f value -c id)
Step 2 — Check service health
Before making versioned calls, confirm the API is up:
curl -s http://<controller>:8788/healthz
A running service returns HTTP 200.
Step 3 — List Protection Groups
With the token and tenant ID in hand, make a versioned API call:
curl -s \
-H "X-Auth-Token: $TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
http://<controller>:8788/v1/$TENANT_ID/protection-groups | jq .
Equivalent CLI command:
openstack dr protection group list
Step 4 — Create a Protection Group
curl -s -X POST \
-H "X-Auth-Token: $TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
-H "Content-Type: application/json" \
-d '{
"protection_group": {
"name": "prod-pg",
"primary_site_id": "<primary-site-id>",
"secondary_site_id": "<secondary-site-id>",
"replication_type": "async",
"description": "Production workloads"
}
}' \
http://<controller>:8788/v1/$TENANT_ID/protection-groups | jq .
Equivalent CLI command:
openstack dr protection group create prod-pg \
--primary-site <primary-site-id> \
--secondary-site <secondary-site-id> \
--replication-type async \
--description "Production workloads"
Step 5 — Trigger an action (failover, failback, test failover)
Failover, failback, and test failover are all triggered via a single action endpoint — POST /v1/{tenant_id}/protection-groups/{pg_id}/action — with an action body that identifies the operation type. There are no separate /failover, /failback, or /test-failover sub-paths.
curl -s -X POST \
-H "X-Auth-Token: $TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
-H "Content-Type: application/json" \
-d '{"failover": {"failover_type": "planned"}}' \
http://<controller>:8788/v1/$TENANT_ID/protection-groups/<pg_id>/action | jq .
Equivalent CLI command:
openstack dr failover <pg-id> --failover-type planned
Step 6 — Monitor the operation
All DR operations are long-running and tracked asynchronously. The action response returns an operation_id. Poll it at the operations endpoint:
curl -s \
-H "X-Auth-Token: $TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
http://<controller>:8788/v1/$TENANT_ID/operations/<operation-id> | jq .
Equivalent CLI command:
openstack dr operation show <operation-id>
The progress field runs from 0 to 100. Poll until status is completed or failed.
Example 1 — Health check (no authentication required)
curl -s http://<controller>:8788/healthz
Expected output:
{"status": "ok"}
HTTP 200 confirms the API process is running and accepting connections.
Example 2 — List Protection Groups
TOKEN=$(openstack token issue -f value -c id)
TENANT_ID=$(openstack project show myproject -f value -c id)
curl -s \
-H "X-Auth-Token: $TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
http://192.0.2.10:8788/v1/$TENANT_ID/protection-groups | jq .
Expected output (empty tenant):
{
"protection_groups": []
}
Expected output (with groups):
{
"protection_groups": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "prod-pg",
"status": "ACTIVE",
"primary_site_id": "site-uuid-1",
"secondary_site_id": "site-uuid-2",
"replication_type": "async"
}
]
}
Example 3 — Create a Protection Group and add a VM
# Create the Protection Group
PG_ID=$(openstack dr protection group create prod-pg \
--primary-site cluster1 \
--secondary-site cluster2 \
--replication-type async \
-f value -c id)
echo "Created PG: $PG_ID"
# Add a Nova instance to the group
openstack dr protection group member add $PG_ID \
--instance <nova-instance-uuid>
Expected output for member add:
+-------------+--------------------------------------+
| Field | Value |
+-------------+--------------------------------------+
| id | m1n2o3p4-q5r6-7890-stuv-wx1234567890 |
| instance_id | <nova-instance-uuid> |
| pg_id | a1b2c3d4-e5f6-7890-abcd-ef1234567890 |
| status | ACTIVE |
+-------------+--------------------------------------+
Example 4 — Trigger a planned failover and monitor it
# Trigger via raw HTTP
curl -s -X POST \
-H "X-Auth-Token: $TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
-H "Content-Type: application/json" \
-d '{"failover": {"failover_type": "planned"}}' \
http://192.0.2.10:8788/v1/$TENANT_ID/protection-groups/$PG_ID/action | jq .
Response (operation accepted):
{
"operation": {
"id": "op-uuid-1234",
"type": "failover",
"status": "in_progress",
"progress": 0,
"protection_group_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}
# Poll until complete
OP_ID="op-uuid-1234"
while true; do
STATUS=$(openstack dr operation show $OP_ID -f value -c status)
PROGRESS=$(openstack dr operation show $OP_ID -f value -c progress)
echo "Status: $STATUS Progress: $PROGRESS%"
if [ "$STATUS" = "completed" ]; then
echo "Failover completed successfully."
break
elif [ "$STATUS" = "failed" ]; then
echo "Failover failed. Inspect with: openstack dr operation show $OP_ID"
exit 1
fi
sleep 10
done
Example 5 — Check replication health
curl -s \
-H "X-Auth-Token: $TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
http://192.0.2.10:8788/v1/$TENANT_ID/protection-groups/$PG_ID/replication-health | jq .
Expected output:
{
"replication_health": {
"status": "healthy",
"lag_seconds": 12,
"rpo_compliant": true,
"recovery_points_available": 8,
"failover_ready": true
}
}
Equivalent CLI command:
openstack dr replication health $PG_ID
Example 6 — Wrong API microversion (error demonstration)
curl -s -X GET \
-H "X-Auth-Token: $TOKEN" \
-H "OpenStack-API-Version: protector 9.9" \
http://192.0.2.10:8788/v1/$TENANT_ID/protection-groups
Expected response — HTTP 400 Bad Request:
{
"badRequest": {
"message": "Invalid microversion: 9.9. Supported versions: 1.0 to 1.1",
"code": 400
}
}
Issue 1 — curl returns Connection refused on port 8788
Symptom: curl: (7) Failed to connect to <controller> port 8788: Connection refused
Likely cause: The protector_api service is not running, or it is bound to a different address.
Fix:
- On the controller node, check the process:
ps aux | grep protector - Check the systemd unit:
systemctl status protector_api - Confirm
bind_hostandbind_portinprotector.confunder the[api]section. Defaults are0.0.0.0and8788. - Start or restart the service:
systemctl restart protector_api
Issue 2 — HTTP 401 Unauthorized on every API call
Symptom: The API returns 401 Unauthorized.
Likely cause: The X-Auth-Token has expired or belongs to a different project than the <tenant_id> in the URL.
Fix:
- Re-issue a token:
TOKEN=$(openstack token issue -f value -c id) - Confirm the project UUID matches:
TENANT_ID=$(openstack project show <project-name> -f value -c id) - Verify the token is for the correct site — tokens issued against cluster1's Keystone are not valid on cluster2.
Issue 3 — HTTP 400 Bad Request immediately on any versioned call
Symptom: Every versioned API call returns 400, even a simple list.
Likely cause: The OpenStack-API-Version header is missing, malformed, or specifies a version outside the supported range (1.0–1.1).
Fix:
- Ensure the header is present and spelled correctly:
OpenStack-API-Version: protector 1.1 - The format is
<service-type> <version>— do not omit theprotectorprefix. - Confirm you are not using a version above
1.1.
Issue 4 — openstack dr commands not found after installation
Symptom: openstack: 'dr' is not an openstack command
Likely cause: The openstack-protector package is installed in a different Python environment than python-openstackclient, or the entry point was not registered.
Fix:
- Confirm both packages share the same environment:
pip list | grep protector - Reinstall from the repository root:
pip install -e . - Check the plugin loaded:
openstack --help | grep "^ dr"
Issue 5 — HTTP 404 on /v1/<tenant_id>/protection-groups
Symptom: The API returns 404 for a valid-looking URL.
Likely cause: The <tenant_id> in the path does not match the project associated with your token, or the URL path has a typo.
Fix:
- Double-check the tenant UUID:
openstack project show <name> -f value -c id - Confirm the full path starts with
/v1/— there is no/dr/prefix and no/v2/version. - Verify the Keystone catalog endpoint is correct:
openstack catalog show protector
Issue 6 — openstack dr protection group list returns an empty list unexpectedly
Symptom: The command succeeds (HTTP 200) but returns no results even though groups exist.
Likely cause: You are authenticated to the wrong project or the wrong site.
Fix:
- Confirm you are sourcing credentials for the correct site and project.
- Admins can add
--all-projectsto see groups across tenants:openstack dr protection group list --all-projects - Check the Protector endpoint in the catalog points to the correct site's controller.
Issue 7 — Action call returns 404 with path /v1/<tenant_id>/protection-groups/<pg_id>/actions
Symptom: POST to /actions (plural) returns HTTP 404.
Likely cause: The action endpoint uses the singular form /action, not /actions.
Fix: Change the URL to POST /v1/{tenant_id}/protection-groups/{pg_id}/action (singular). There are no separate /failover, /failback, or /test-failover sub-paths — all are sent to /action with a typed body.