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 discover the API version, obtain a Keystone token, and call a real resource endpoint — all using curl. By the end you will understand the request shape every subsequent API call follows: where to send it, which headers are required, and how to read the response envelope.


Prerequisites

Before you begin, ensure you have:

  • Two OpenStack deployments running Victoria or later, with the Trilio Site Recovery service deployed on each via the Juju charm
  • curl (or any HTTP client such as HTTPie or Python requests)
  • python-openstackclient installed via pip — not the snap; the snap is incompatible with the triliositerecovery CLI plugin
  • Keystone credentials for a user who has the member and dr_operator roles granted on their project on both sites — these roles must be pre-created before any cross-site operation
  • Network access to the primary site's Keystone endpoint (port 5000) and protector-api endpoint (port 8788)
  • The Tenant (Project) ID of your OpenStack project — used in every versioned API URL

Tip: Export your environment variables from your site's openrc file (source primary-site-openrc.sh) before running the examples below. The commands reference $OS_AUTH_URL, $OS_USERNAME, $OS_PASSWORD, $OS_PROJECT_NAME, and $OS_USER_DOMAIN_NAME throughout.


Installation

The CLI plugin is a separate Python package from the server. Installing the server package does not install the CLI.

  1. Install python-openstackclient via pip (not the snap):

    pip install python-openstackclient
    
  2. Install the triliositerecovery CLI plugin from the client/ subdirectory of the repository:

    pip install -e ./client
    

    This registers the openstack dr command group with the OpenStack CLI via the triliositerecovery entry point.

  3. Verify the plugin is loaded:

    openstack --help | grep '^  dr'
    openstack help dr
    

    You should see a list of dr subcommands including site, protection group, failover, and failback.

  4. Source your site credentials:

    source primary-site-openrc.sh
    

Note: The openstackclient snap is incompatible with the triliositerecovery plugin. If you have the snap installed, remove it and use the pip-installed package instead.


Configuration

Every request to the protector-api shares the same three configuration concerns:

API base URL

The protector-api listens on port 8788 by default. The bind address and port are controlled by the [api] section of protector.conf:

OptionSectionDefaultEffect
bind_host[api]0.0.0.0Interface the API binds to
bind_port[api]8788TCP port the API listens on
workers[api]4 (compiled-in); 2 in Juju charm deploymentsNumber of WSGI worker processes

In Juju charm deployments, do not edit protector.conf directly — the charm overwrites it on every config-changed event. Use juju config with hyphenated option names instead:

juju config trilio-site-recovery bind-port=8788

URL structure

All versioned resource endpoints require the /v1/{tenant_id}/ prefix. A URL such as /{tenant_id}/sites returns 404 — the /v1/ segment is mandatory.

The only endpoints that do not require a tenant ID prefix are:

  • GET / — version discovery (no authentication required)

Authentication header

All resource endpoints require an X-Auth-Token header containing a token obtained from Keystone (port 5000). The protector-api has no authentication endpoint of its own.

Microversion header

The OpenStack-API-Version: protector <version> header is optional. When omitted, the middleware defaults to microversion 1.0. The supported range is 1.0–1.1. Sending a value outside this range returns HTTP 400.

HeaderRequiredDefault when omitted
X-Auth-TokenYes (on resource endpoints)—
OpenStack-API-VersionNoprotector 1.0
Content-Type: application/jsonOn POST/PUT—

Usage

Making an API request to Trilio Site Recovery follows three steps every time:

  1. Discover the API version — confirm the service is reachable and learn the supported microversion range.
  2. Obtain a Keystone token — authenticate with Keystone on port 5000 and capture the X-Subject-Token from the response headers.
  3. Call a resource endpoint — send your authenticated request with the token in X-Auth-Token.

Step 1 — Discover the API version

The version discovery endpoint requires no authentication and returns the supported microversion range:

PROTECTOR_URL="http://<primary-site-host>:8788"

curl -s -X GET "${PROTECTOR_URL}/" \
  -H "Accept: application/json" | python3 -m json.tool

The response wraps version information in a versions array:

{
  "versions": [
    {
      "id": "v1.0",
      "status": "CURRENT",
      "version": "1.1",
      "min_version": "1.0",
      "links": [
        {
          "rel": "self",
          "href": "http://<primary-site-host>:8788/v1/"
        }
      ]
    }
  ]
}

Key fields:

  • version — the maximum supported microversion (1.1)
  • min_version — the minimum supported microversion (1.0)
  • links[0].href — the versioned root URL to use for all resource requests

Step 2 — Obtain a Keystone token

Authenticate with Keystone on port 5000. The token is returned in the X-Subject-Token response header:

KEYSTONE_URL="http://<primary-site-host>:5000"

TOKEN=$(curl -s -i -X POST "${KEYSTONE_URL}/v3/auth/tokens" \
  -H "Content-Type: application/json" \
  -d '{
    "auth": {
      "identity": {
        "methods": ["password"],
        "password": {
          "user": {
            "name": "'"${OS_USERNAME}"'",
            "domain": {"name": "'"${OS_USER_DOMAIN_NAME}"'"},
            "password": "'"${OS_PASSWORD}"'"
          }
        }
      },
      "scope": {
        "project": {
          "name": "'"${OS_PROJECT_NAME}"'",
          "domain": {"name": "Default"}
        }
      }
    }
  }' | grep -i 'x-subject-token' | awk '{print $2}' | tr -d '\r')

echo "Token: ${TOKEN}"

Note: Authentication is handled entirely by Keystone. The protector-api has no /auth or token endpoint.

Step 3 — Call a resource endpoint

With your token set, call any versioned resource endpoint. Substitute your project UUID for {tenant_id}:

TENANT_ID=$(openstack project show "${OS_PROJECT_NAME}" -f value -c id)

curl -s -X GET "${PROTECTOR_URL}/v1/${TENANT_ID}/sites" \
  -H "X-Auth-Token: ${TOKEN}" \
  -H "Accept: application/json" \
  -H "OpenStack-API-Version: protector 1.1" | python3 -m json.tool

Examples

Example 1 — Version discovery

Confirm the service is reachable and read the supported microversion range. No authentication needed.

curl -s -X GET "http://<primary-site-host>:8788/" \
  -H "Accept: application/json" | python3 -m json.tool

Expected response (HTTP 200):

{
  "versions": [
    {
      "id": "v1.0",
      "status": "CURRENT",
      "version": "1.1",
      "min_version": "1.0",
      "links": [
        {
          "rel": "self",
          "href": "http://<primary-site-host>:8788/v1/"
        }
      ]
    }
  ]
}

Example 2 — Full first-request flow using environment variables

This script sources your OpenRC, obtains a token from Keystone, resolves your tenant ID, and lists Protection Groups.

#!/bin/bash
set -euo pipefail

# 1. Source credentials
source primary-site-openrc.sh

PROTECTOR_URL="http://<primary-site-host>:8788"
KEYSTONE_URL="${OS_AUTH_URL}"   # e.g. http://<host>:5000

# 2. Obtain Keystone token
echo "Authenticating with Keystone..."
TOKEN=$(curl -s -i -X POST "${KEYSTONE_URL}/v3/auth/tokens" \
  -H "Content-Type: application/json" \
  -d "{
    \"auth\": {
      \"identity\": {
        \"methods\": [\"password\"],
        \"password\": {
          \"user\": {
            \"name\": \"${OS_USERNAME}\",
            \"domain\": {\"name\": \"${OS_USER_DOMAIN_NAME}\"},
            \"password\": \"${OS_PASSWORD}\"
          }
        }
      },
      \"scope\": {
        \"project\": {
          \"name\": \"${OS_PROJECT_NAME}\",
          \"domain\": {\"name\": \"Default\"}
        }
      }
    }
  }" | grep -i 'x-subject-token' | awk '{print $2}' | tr -d '\r')

[ -z "${TOKEN}" ] && { echo "ERROR: Failed to obtain token"; exit 1; }
echo "Token obtained."

# 3. Resolve tenant ID
TENANT_ID=$(openstack project show "${OS_PROJECT_NAME}" -f value -c id)
echo "Tenant ID: ${TENANT_ID}"

# 4. List Protection Groups
echo "Listing Protection Groups..."
curl -s -X GET "${PROTECTOR_URL}/v1/${TENANT_ID}/protection-groups" \
  -H "X-Auth-Token: ${TOKEN}" \
  -H "Accept: application/json" \
  -H "OpenStack-API-Version: protector 1.1" | python3 -m json.tool

Expected response when no Protection Groups exist yet (HTTP 200):

{
  "protection_groups": []
}

Example 3 — List registered sites

Once you have a token and tenant ID, list the DR sites registered with the service:

curl -s -X GET "http://<primary-site-host>:8788/v1/${TENANT_ID}/sites" \
  -H "X-Auth-Token: ${TOKEN}" \
  -H "Accept: application/json" \
  -H "OpenStack-API-Version: protector 1.1" | python3 -m json.tool

Expected response (HTTP 200):

{
  "sites": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "primary-cluster",
      "site_type": "PRIMARY",
      "status": "ACTIVE",
      "auth_url": "http://<primary-site-host>:5000"
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "name": "secondary-cluster",
      "site_type": "SECONDARY",
      "status": "ACTIVE",
      "auth_url": "http://<secondary-site-host>:5000"
    }
  ]
}

Example 4 — Send an unsupported microversion (expected HTTP 400)

If you send a microversion outside the supported 1.0–1.1 range, the middleware rejects the request:

curl -s -X GET "http://<primary-site-host>:8788/v1/${TENANT_ID}/sites" \
  -H "X-Auth-Token: ${TOKEN}" \
  -H "OpenStack-API-Version: protector 9.9" | python3 -m json.tool

Expected response (HTTP 400):

{
  "error": {
    "code": 400,
    "message": "Invalid API version '9.9'. Supported versions: 1.0 to 1.1"
  }
}

Note that the response also carries OpenStack-API-Version and OpenStack-API-Minimum-Version headers telling you the valid range.


Example 5 — Equivalent CLI call

The same list-sites operation via the openstack dr CLI (which handles token acquisition automatically):

source primary-site-openrc.sh
openstack dr site list

Add -f json for machine-readable output:

openstack dr site list -f json

Troubleshooting

HTTP 404 on a resource URL

Symptom: curl returns HTTP 404 for a URL like http://<host>:8788/<tenant_id>/sites.

Cause: The /v1/ prefix is missing. All versioned endpoints require the /v1/{tenant_id}/ prefix. URLs without /v1/ return 404.

Fix: Change the URL to http://<host>:8788/v1/<tenant_id>/sites.


HTTP 400 — Invalid API version

Symptom: Response body contains "Invalid API version" and HTTP status is 400.

Cause: The OpenStack-API-Version header contains a version string outside the supported 1.0–1.1 range, or is malformed (e.g. missing the service name prefix protector).

Fix: Use OpenStack-API-Version: protector 1.1 or omit the header entirely (the service defaults to 1.0).


HTTP 401 — Unauthorized

Symptom: Resource endpoint returns HTTP 401.

Cause: The X-Auth-Token header is missing, expired, or was issued for a different project.

Fix: Re-authenticate with Keystone and capture a fresh token. Ensure the token was scoped to the same project whose UUID you are using in the URL path.


HTTP 403 — Forbidden

Symptom: Request returns HTTP 403 on a site or operation endpoint.

Cause: Your user lacks the required Keystone role. DR users need member + dr_operator on their project. Site-level operations additionally require dr_site_admin.

Fix: Ask your cloud administrator to grant the missing roles. Both dr_operator and dr_site_admin must be pre-created on both sites before any cross-site operation.


Token obtained but tenant ID is wrong

Symptom: API returns HTTP 404 or empty results even though the service is running.

Cause: The tenant ID in the URL does not match the project the token is scoped to.

Fix: Resolve the project UUID from the same credentials you used to authenticate:

TENANT_ID=$(openstack project show "${OS_PROJECT_NAME}" -f value -c id)

Use this value in the URL path (/v1/${TENANT_ID}/...).


openstack dr commands not found

Symptom: Running openstack dr site list returns 'dr' is not an openstack command.

Cause: The triliositerecovery CLI plugin is not installed, or you installed the server package (pip install -e . from the repo root) instead of the client package.

Fix: Install the client package separately:

pip install -e ./client

Also ensure you are using the pip-installed openstack binary, not the snap — the snap is incompatible with the plugin.


Connection refused on port 8788

Symptom: curl: (7) Failed to connect to <host> port 8788: Connection refused.

Cause: The protector-api service is not running, or the host/port is incorrect.

Fix: On the Juju-deployed host, check the service status:

juju status trilio-site-recovery

If the unit is in an error state, check logs:

juju debug-log --include trilio-site-recovery

Confirm that port 8788 is open between your client and the API host.


Cross-site operations fail with RemoteSiteUnreachable

Symptom: Failover or metadata sync operations return a RemoteSiteUnreachable error.

Cause: Port 8788 between the two sites is blocked in one direction. Cross-site connectivity on port 8788 must be bidirectional — one-way connectivity causes metadata sync and tenant mapping failures.

Fix: Ensure both sites can reach each other on port 8788. Verify with:

curl -s -X GET "http://<secondary-site-host>:8788/"

from the primary site's host, and vice versa.