Trilio Site Recovery for OpenStackDisaster Recovery Solution for OpenStack Virtual Machines
Guide

Your First Request

Example API call with curl or HTTP client


Overview

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.


Prerequisites

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 use curl
  • python-openstackclient installed if you want to use the openstack dr CLI instead of raw HTTP
  • jq (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)

Installation

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

Configuration

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)

SectionOptionDefaultPurpose
[api]bind_host0.0.0.0Address the API WSGI server binds to
[api]bind_port8788Port the API listens on
[api]workers4Number of API worker processes
[database]connectionMariaDB/MySQL connection string
[DEFAULT]storage_driverpure_flasharrayStorage backend driver (pure_flasharray, drbd, or mock)

Configuration is managed entirely through protector.conf files; there is no /v1/config endpoint.


Usage

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.


Examples

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
  }
}

Troubleshooting

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:

  1. On the controller node, check the process: ps aux | grep protector
  2. Check the systemd unit: systemctl status protector_api
  3. Confirm bind_host and bind_port in protector.conf under the [api] section. Defaults are 0.0.0.0 and 8788.
  4. 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:

  1. Re-issue a token: TOKEN=$(openstack token issue -f value -c id)
  2. Confirm the project UUID matches: TENANT_ID=$(openstack project show <project-name> -f value -c id)
  3. 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.01.1).

Fix:

  1. Ensure the header is present and spelled correctly: OpenStack-API-Version: protector 1.1
  2. The format is <service-type> <version> — do not omit the protector prefix.
  3. 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:

  1. Confirm both packages share the same environment: pip list | grep protector
  2. Reinstall from the repository root: pip install -e .
  3. 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:

  1. Double-check the tenant UUID: openstack project show <name> -f value -c id
  2. Confirm the full path starts with /v1/ — there is no /dr/ prefix and no /v2/ version.
  3. 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:

  1. Confirm you are sourcing credentials for the correct site and project.
  2. Admins can add --all-projects to see groups across tenants: openstack dr protection group list --all-projects
  3. 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.