Getting Started
Making your first API request
This tutorial walks you through making your first authenticated request to the Trilio Site Recovery API. You will discover the API version, obtain a Keystone token, and list your Protection Groups — confirming that the service is reachable and your credentials are valid. By the end you will have a working request pattern you can build on for every subsequent API call, whether you are writing a script, integrating with a CI pipeline, or exploring the service interactively.
Before you begin, make sure you have the following in place:
- Trilio Site Recovery deployed on at least one OpenStack site via the Juju charm (
trilio-site-recovery-for-openstack). Theprotector-apiservice must be running and reachable on port 8788. - OpenStack credentials for the site you are targeting — either an
openrcfile or environment variables (OS_AUTH_URL,OS_USERNAME,OS_PASSWORD,OS_PROJECT_NAME, etc.). - Keystone roles granted on your project:
member+dr_operatoron your own project (required for tenant DR operations).- Both
dr_operatoranddr_site_adminroles must exist on both sites before any cross-site operation. If they are missing, trust creation fails withRole <name> does not exist on site <site>.
python-openstackclientinstalled viapip(not the snap — the snap is incompatible with thetriliositerecoveryCLI plugin):pip install python-openstackclienttriliositerecoveryCLI plugin installed separately from theclient/subdirectory (installing the server package does not install the CLI):Or from PyPI:pip install -e ./clientpip install triliositerecoverycurlandjqavailable if you prefer raw HTTP examples.- Network connectivity from your workstation to the
protector-apion port 8788 and to Keystone on port 5000. - OpenStack Victoria or later on each site.
The minimal path to confirm the API is working:
-
Source your OpenStack credentials.
source ~/openrc -
Confirm the service is healthy (no authentication required).
curl -s http://<protector-api-host>:8788/healthzExpected output:
{"status": "ok", "checks": {"database": "ok"}} -
Discover the supported API versions (no authentication required).
curl -s http://<protector-api-host>:8788/ -
Obtain a Keystone token for your project.
export OS_TOKEN=$(openstack token issue -f value -c id) export OS_PROJECT_ID=$(openstack token issue -f value -c project_id) -
List your Protection Groups using the token.
curl -s \ -H "X-Auth-Token: $OS_TOKEN" \ http://<protector-api-host>:8788/v1/${OS_PROJECT_ID}/protection-groups -
Make the same call with the CLI to confirm the plugin is wired up.
openstack dr protection group list
The following procedure explains each request in detail, including what success looks like at every stage.
Step 1 — Verify liveness
The /healthz endpoint requires no authentication and tells you whether protector-api can reach its database. Query it first so you know the service is up before troubleshooting credentials.
curl -s http://<protector-api-host>:8788/healthz
Success: HTTP 200 with body {"status": "ok", "checks": {"database": "ok"}}.
Failure: HTTP 503 with body {"status": "unhealthy", "checks": {"database": "<exception class name>"}} means the service started but cannot reach its MariaDB/MySQL instance. Check the database connection string in /etc/protector/protector.conf (Juju charm deployments: use juju config trilio-site-recovery — do not edit the file directly).
Step 2 — Discover the API version
The version discovery endpoint lives at GET / on port 8788. It also requires no authentication. The response tells you the maximum microversion (1.1) and the minimum (1.0) the server accepts, and gives you the versioned root URL to use for all subsequent calls.
curl -s http://<protector-api-host>:8788/
The OpenStack-API-Version header is optional on every request. When omitted, the middleware defaults to microversion 1.0. You only need to send it when you want behaviour introduced in 1.1, or to explicitly pin a version in automation:
curl -s \
-H "OpenStack-API-Version: protector 1.1" \
http://<protector-api-host>:8788/
Success: HTTP 200. See the Examples section for the exact response shape.
Step 3 — Obtain a Keystone token
All versioned resource endpoints (/v1/{tenant_id}/…) require an X-Auth-Token obtained from Keystone on port 5000. The protector-api has no authentication endpoint of its own.
source ~/openrc
export OS_TOKEN=$(openstack token issue -f value -c id)
export OS_PROJECT_ID=$(openstack token issue -f value -c project_id)
Verify both variables are populated:
echo "Token: ${OS_TOKEN:0:20}…"
echo "Project ID: $OS_PROJECT_ID"
Success: Both variables are non-empty. The token is a long opaque string; the project ID is a UUID.
Step 4 — List Protection Groups
With a valid token and your project ID, you can make your first authenticated request. The Protection Groups endpoint is the natural starting point because it shows you every group your project owns or has access to.
curl -s \
-H "X-Auth-Token: $OS_TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
http://<protector-api-host>:8788/v1/${OS_PROJECT_ID}/protection-groups | jq .
Success: HTTP 200 with a JSON body containing a protection_groups array (empty if none have been created yet).
What the URL structure means:
/v1/— required version prefix; URLs without it return 404.{tenant_id}— your OpenStack project UUID, scoping results to your project./protection-groups— the resource collection.
Step 5 — Make the same call with the CLI
The openstack dr plugin wraps the same HTTP calls shown above. Confirming it works ensures your plugin installation, endpoint discovery via the Keystone service catalog, and credentials are all correct.
openstack dr protection group list
Success: A table is printed (or an empty table with headers if no Protection Groups exist). If you see openstack: 'dr' is not an openstack command, the triliositerecovery package is not installed or your openstack binary is the incompatible snap version.
Step 6 — Explore registered sites
List the DR sites your service knows about. This confirms cross-site registration is complete and lets you collect site IDs needed for creating Protection Groups later.
openstack dr site list
Or via the API:
curl -s \
-H "X-Auth-Token: $OS_TOKEN" \
http://<protector-api-host>:8788/v1/${OS_PROJECT_ID}/sites | jq .
Success: A list containing at least one site record with id, name, and status fields.
Example 1 — Version discovery
Query the API root to confirm the service is running and learn which microversions it supports.
curl -s http://<protector-api-host>:8788/ | jq .
Expected response (HTTP 200):
{
"versions": [
{
"id": "v1.0",
"status": "CURRENT",
"version": "1.1",
"min_version": "1.0",
"links": [
{
"rel": "self",
"href": "http://<protector-api-host>:8788/v1"
}
]
}
]
}
Key fields to note:
version— the maximum microversion the server accepts (1.1).min_version— the lowest version the server accepts (1.0).links[].href— the versioned root to prepend to all resource paths.
Example 2 — Liveness check
Poll /healthz to confirm the API and its database connection are healthy. This is safe to call from monitoring scripts; it requires no token.
curl -s http://<protector-api-host>:8788/healthz
Healthy response (HTTP 200):
{"status": "ok", "checks": {"database": "ok"}}
Unhealthy response (HTTP 503):
{"status": "unhealthy", "checks": {"database": "OperationalError"}}
Example 3 — Obtain a Keystone token and project ID
source ~/openrc
export OS_TOKEN=$(openstack token issue -f value -c id)
export OS_PROJECT_ID=$(openstack token issue -f value -c project_id)
echo "Project: $OS_PROJECT_ID"
echo "Token prefix: ${OS_TOKEN:0:30}…"
Expected output:
Project: a3b4c5d6-e7f8-1234-abcd-000000000001
Token prefix: gAAAAABl…
Example 4 — List Protection Groups (REST)
curl -s \
-H "X-Auth-Token: $OS_TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
"http://<protector-api-host>:8788/v1/${OS_PROJECT_ID}/protection-groups" | jq .
Response when no Protection Groups exist yet (HTTP 200):
{
"protection_groups": []
}
Response with one Protection Group (HTTP 200):
{
"protection_groups": [
{
"id": "pg-uuid-0001",
"name": "web-tier-pg",
"status": "active",
"primary_site_id": "site-uuid-0001",
"secondary_site_id": "site-uuid-0002",
"replication_type": "async",
"created_at": "2024-01-15T10:00:00Z"
}
]
}
Example 5 — List Protection Groups (CLI)
openstack dr protection group list
Expected output (no groups):
+----+------+--------+--------------+----------------+------------------+
| ID | Name | Status | Primary Site | Secondary Site | Replication Type |
+----+------+--------+--------------+----------------+------------------+
+----+------+--------+--------------+----------------+------------------+
Example 6 — List registered sites (CLI)
openstack dr site list
Expected output:
+--------------------------------------+-----------+--------+------+
| ID | Name | Status | Type |
+--------------------------------------+-----------+--------+------+
| site-uuid-0001 | primary | ACTIVE | PRIMARY |
| site-uuid-0002 | secondary | ACTIVE | SECONDARY |
+--------------------------------------+-----------+--------+------+
Example 7 — Request with an explicit microversion header
Send OpenStack-API-Version: protector 1.1 to request the latest behaviour. Omitting the header is also valid and defaults to 1.0.
curl -s \
-H "X-Auth-Token: $OS_TOKEN" \
-H "OpenStack-API-Version: protector 1.1" \
"http://<protector-api-host>:8788/v1/${OS_PROJECT_ID}/sites" | jq .
Sending an out-of-range version returns HTTP 400:
curl -i \
-H "X-Auth-Token: $OS_TOKEN" \
-H "OpenStack-API-Version: protector 9.9" \
"http://<protector-api-host>:8788/v1/${OS_PROJECT_ID}/sites"
Response (HTTP 400):
HTTP/1.1 400 Bad Request
...
{"code": 400, "message": "Version 9.9 is not supported. Supported versions: 1.0 to 1.1"}
Use the following table as a first-pass diagnostic. Each issue lists the symptom you observe, the most likely cause, and the concrete action to fix it.
openstack: 'dr' is not an openstack command
Cause: The triliositerecovery CLI plugin is not installed, or you are using the openstack snap instead of the pip-installed binary.
Fix:
- Confirm you are not using the snap:
which openstackshould point to a path under your Python environment (e.g./usr/local/bin/openstack), not/snap/bin/openstack. The snap is incompatible with third-party CLI plugins. - Install the plugin:
pip install triliositerecovery(PyPI) orpip install -e ./clientfrom the repository root. Runningpip install -e .from the repo root installs only the server binaries — the CLI requires the separate./clientinstall. - Re-run
openstack dr protection group list.
HTTP 404 on every resource URL
Cause: The URL is missing the required /v1/ prefix, or the path is misspelled.
Fix: All versioned resource endpoints require the form /v1/{tenant_id}/<resource>. A URL such as /{tenant_id}/protection-groups (no /v1/) returns 404. Correct form:
curl -H "X-Auth-Token: $OS_TOKEN" \
http://<host>:8788/v1/${OS_PROJECT_ID}/protection-groups
HTTP 401 Unauthorized on API calls
Cause: The X-Auth-Token is missing, expired, or scoped to the wrong project.
Fix:
- Confirm your
openrcis sourced:echo $OS_AUTH_URLshould be non-empty. - Re-issue a token:
export OS_TOKEN=$(openstack token issue -f value -c id). - Confirm the token's project matches the
{tenant_id}in the URL:export OS_PROJECT_ID=$(openstack token issue -f value -c project_id). - Verify you have the
memberanddr_operatorroles on your project:openstack role assignment list --user $OS_USERNAME --project $OS_PROJECT_NAME --names.
HTTP 400 Bad Request with message about API version
Cause: The OpenStack-API-Version header contains a value outside the supported range 1.0–1.1, or is malformed.
Fix: Use a supported value or omit the header entirely (the server defaults to 1.0):
-H "OpenStack-API-Version: protector 1.1"
Do not use values such as protector 2.0 or protector latest.
/healthz returns HTTP 503 with {"status": "unhealthy", "checks": {"database": "OperationalError"}}
Cause: protector-api cannot connect to its MariaDB/MySQL database.
Fix (Juju charm deployments):
- Check the database relation is satisfied:
juju status trilio-site-recovery— look forblockedorwaitingstatus. - Do not edit
/etc/protector/protector.confdirectly; the charm overwrites it on everyconfig-changedevent. Usejuju configto set options. - If the relation is established, check database logs:
juju ssh trilio-site-recovery/0 'journalctl -u protector_engine --no-pager -n 50'.
curl http://<host>:8788/ returns connection refused
Cause: The protector-api service is not running, or it is bound to a different address.
Fix (Juju charm deployments):
- Check service status:
juju status trilio-site-recovery. - SSH to the unit and check the systemd service:
juju ssh trilio-site-recovery/0 'systemctl status protector_api'. - Confirm the bind address: the charm defaults to
0.0.0.0:8788. Override withjuju config trilio-site-recovery bind-port=8788if needed. - Check logs under
/var/log/protector/.
Trust creation failed: Role dr_operator does not exist on site <name>
Cause: The dr_operator (or dr_site_admin) Keystone role has not been created on one or both sites. The service looks up roles and grants them; it never creates them.
Fix: Create the missing roles on both sites before any cross-site operation:
openstack role create dr_operator
openstack role create dr_site_admin
Then grant them to your user and to the protector service account on each project that will use DR.
openstack dr site list returns an empty list after deployment
Cause: Sites have not yet been registered. The service does not auto-discover OpenStack clusters.
Fix: Register each site using openstack dr site create. The command requires --site-type, --auth-url, and additional mandatory flags — run openstack dr site create --help for the full required argument list. After registering both sites, re-run openstack dr site list.