Trilio Site Recovery for OpenStackOpenStack native DR service
Guide

Prerequisites

OpenStack release, Keystone, message queue, database, required services


Overview

This page lists every prerequisite for deploying Trilio Site Recovery for OpenStack: the OpenStack release floor, required supporting services, network connectivity between sites, storage backend requirements, and the Keystone roles and Cinder policy relaxations the service depends on. Meeting these prerequisites before deployment avoids failures that surface late — during a first failover rather than at install time — and are difficult to distinguish from product defects. Work through this list on both sites before proceeding to the deployment guide for your chosen deployment tool (Juju, Kolla-Ansible, or OpenShift).


Prerequisites

OpenStack release

  • Two independent OpenStack clusters or regions, each running OpenStack Victoria or later.
  • Both clusters must have Keystone, Nova, Cinder, and Neutron. Glance is required for image lookup during certain recovery operations. Barbican is required if you use volume encryption (note: LUKS-encrypted volumes are not supported — see the Limitations section below).

Supporting services

Each site must provide:

ServicePurpose
MariaDB or MySQLStores all service state (sites, protection groups, members, volumes, DR operations, tenant mappings). Schema managed by Alembic; database name protector, user protector.
RabbitMQoslo.messaging RPC transport between protector-api and protector-engine. Default vhost protector, default user protector.
KeystoneToken validation, RBAC enforcement, service catalog registration. The service registers under service type disaster-recovery (with protector as an additional alias).

Operating system

  • Ubuntu 22.04 LTS (Jammy) or Ubuntu 24.04 LTS (Noble) — required for Juju charm and Kolla-Ansible deployments.
  • Red Hat OpenShift (RHOSO18) — for OpenShift deployments only.

Network connectivity between sites

Both sites must be able to reach each other on the following ports. Firewall rules or security groups must permit these in both directions:

PortService
8788Trilio Site Recovery API (protector-api)
5000Keystone
8774Nova
8776Cinder

Name resolution across sites is a common failure point. Each site's protector-engine must resolve the other site's Keystone and API endpoints by the hostnames those services are registered under. If your sites run separate DNS resolvers, configure cross-site name resolution before deployment. For Juju deployments the extra-hosts charm config option provides a workaround where DNS changes are not immediately available.

Keystone roles

Create both roles on both sites before deploying the service. Trust creation fails at the first DR operation if either role is absent, rather than at install time:

openstack role create dr_site_admin
openstack role create dr_operator

dr_site_admin gates site registration, tenant mapping, and site-level failover. It is cloud-wide regardless of which project it is granted on — assign it only to cloud operators on the admin project, never per DR project.

dr_operator gates per-project DR operations and is delegated by every per-tenant Keystone trust the service creates. Grant it to DR users on each DR project on both sites.

The Juju charm creates both roles automatically via keystone-requested-roles. If you are deploying with Kolla-Ansible or OpenShift, create them manually as shown above.

Cinder policy relaxations

The service runs DR operations as a tenant through Keystone trusts, not as admin. Cinder's default policy restricts several APIs to admin. You must relax five rules on both sites:

{
  "volume_extension:volume_manage": "rule:admin_or_owner",
  "volume_extension:volume_unmanage": "rule:admin_or_owner",
  "volume_extension:services:index": "rule:admin_or_owner",
  "volume_extension:type_get": "rule:admin_or_owner",
  "volume_extension:type_get_all": "rule:admin_or_owner"
}

Failover imports replicated volumes through Cinder's manage API. Without volume_manage and volume_unmanage, failover fails partway through with HTTP 403, after the storage work is already done. The type_get and type_get_all rules are read-only lookups; some Cinder versions leave them open by default, which is why a lab environment may work without them while a production cloud does not.

For Juju deployments, apply the override with:

juju attach-resource cinder policyd-override=cinder-policy-override.zip

Cinder volume type

On both sites, at least one Cinder volume type must have replication enabled:

openstack volume type set \
  --property replication_enabled=True \
  --property replication_type=async \
  <your-replicated-volume-type>

This is required for --replication-type async protection groups. For active_dr groups, --volume-type is not required — that replication mode is exempt.

Storage backend

For production use: Pure Storage FlashArray with async replication configured between both sites. The python3-pure-client Debian package (Pure Storage REST API SDK) must be installed on every node running protector-engine when the pure driver is active. Install it from the Trilio apt repository.

For evaluation without hardware: The mock storage driver is available and requires no physical arrays. It is the default (storage-driver = mock). State is stored under /var/lib/protector. Do not use the mock driver in production.

Trilio apt repository

The Trilio packages are served from:

deb [trusted=yes] https://apt.fury.io/trilio-os-site-recovery-master/ /

This must be configured on controller and client nodes before installing any Trilio package. The Juju charm writes this automatically to /etc/apt/sources.list.d/trilio-site-recovery.list. For Kolla-Ansible and manual deployments, add it yourself:

echo "deb [trusted=yes] https://apt.fury.io/trilio-os-site-recovery-master/ /" \
  | sudo tee /etc/apt/sources.list.d/trilio-site-recovery.list
sudo apt-get update

Private Gemfury repositories require a read token in the URL:

deb [trusted=yes] https://<read-token>@apt.fury.io/<repo>/ /

Optional: Juju

Required only for charm-based deployment. Any Juju version compatible with Ubuntu Jammy or Noble machines in a Charmed OpenStack model.

Optional: kubectl / oc CLI

Required only for RHOSO18 (OpenShift) deployment.

Limitations to be aware of before deployment

  • LUKS-encrypted volumes are not supported. Adding a member whose volume is LUKS-encrypted raises InvalidInput (HTTP 400). The engine cannot associate Barbican keys on the DR site. Use array-side encryption (Pure Storage data-at-rest encryption) instead.
  • The local_site option is required. The [DEFAULT] local_site configuration option must be set to the name of the DR site this instance belongs to before the service can participate in any DR operation. On Juju, set the local-site charm config option; the unit will remain in BLOCKED state until it is set.
  • Instance names change after failover. Failover creates recovered instances as <name>-failover; failback creates them as <name>-restored. The original name is not restored. Update any external systems (DNS, monitoring, CMDB) that are keyed on instance names.
  • No estimated completion time for DR operations. The service tracks operation progress as a percentage (0–100) but does not compute or expose an estimated completion time for failover, failback, or test failover.

Installation

Installation steps depend on your deployment model. The Trilio apt repository must be configured before installing any package.

Juju (Charmed OpenStack)

The Juju charm installs and configures all packages automatically.

1. Deploy the charm from Charmhub:

juju deploy trilio-site-recovery-for-openstack \
  --channel latest/stable \
  --config local-site=<site-name>

Replace <site-name> with the name you will use when registering this site (e.g., site-primary or site-dr). The unit will remain in BLOCKED state until local-site is set.

2. Add charm relations:

The charm's shared-db relation uses the mysql-shared interface, which mysql-innodb-cluster does not serve directly — connect it through a mysql-router subordinate:

juju deploy mysql-router trilio-site-recovery-mysql-router
juju relate trilio-site-recovery-for-openstack:shared-db trilio-site-recovery-mysql-router:shared-db
juju relate trilio-site-recovery-mysql-router:db-router mysql-innodb-cluster:db-router
juju relate trilio-site-recovery-for-openstack:amqp rabbitmq-server:amqp
juju relate trilio-site-recovery-for-openstack:identity-credentials keystone:identity-credentials

3. Run the database migration:

juju run trilio-site-recovery-for-openstack/leader db-sync

4. Register the service in Keystone:

juju run trilio-site-recovery-for-openstack/leader register-service

5. Verify the deployment:

juju run trilio-site-recovery-for-openstack/leader health-check

6. (Optional) Install the Horizon dashboard plugin:

juju deploy trilio-site-recovery-for-openstack-dashboard \
  --channel latest/stable
juju relate trilio-site-recovery-for-openstack-dashboard:dashboard-plugin openstack-dashboard:dashboard-plugin
juju run trilio-site-recovery-for-openstack-dashboard/leader install-plugin

7. (Optional) Install the CLI on client nodes not managed by the charm:

The charm installs python3-triliositerecovery on the charm unit. On any other machine where you want the openstack dr commands:

sudo apt-get install -y python3-triliositerecovery

Kolla-Ansible (bare-metal / VM)

1. Configure the Trilio apt repository on each node:

echo "deb [trusted=yes] https://apt.fury.io/trilio-os-site-recovery-master/ /" \
  | sudo tee /etc/apt/sources.list.d/trilio-site-recovery.list
sudo apt-get update

2. Run the Kolla install script:

bash deploy/kolla/install.sh

This deploys the protector_api and protector_engine containers using the protector:latest Docker image alongside your Kolla-Ansible managed services.

3. Run the database migration manually (the script does not invoke the Juju charm action):

protector-manage db sync

If you are upgrading across migration 022_add_reconcile_operation_type, you must run the preflight check before db sync:

protector-manage db preflight
protector-manage db sync

4. Install the CLI on client nodes:

sudo apt-get install -y python3-triliositerecovery

OpenShift / RHOSO18

Use the provided install script, which applies all twelve manifests in the correct order and runs the required init Jobs (database init, RabbitMQ init, Keystone registration, schema migration). Do not apply individual manifests manually — the init Jobs and their completion gates are required.

1. Run the installer:

bash deploy/openshift/scripts/install.sh

The installer accepts flags including --namespace, --trilio-ns, --openstack-ns, --image, and --rotate-passwords. Run with --help for the full list.

The installer:

  • Creates the trilio-site-recovery namespace
  • Applies the ServiceAccount, ConfigMap, and Secrets
  • Runs and waits for the db-init, rabbitmq-init, keystone-init, and db-sync Jobs
  • Creates the Service and Route
  • Deploys the trilio-site-recovery-api and trilio-site-recovery-engine Deployments

2. Verify the pods are running:

oc -n trilio-site-recovery get pods \
  -l app.kubernetes.io/name=trilio-site-recovery

To target only the API pods:

oc -n trilio-site-recovery get pods \
  -l app.kubernetes.io/name=trilio-site-recovery,app.kubernetes.io/component=api

To target only the engine pods:

oc -n trilio-site-recovery get pods \
  -l app.kubernetes.io/name=trilio-site-recovery,app.kubernetes.io/component=engine

Note: On OpenShift, the service logs to stdout only. Use oc logs to read service output — there is no log file on disk:

oc -n trilio-site-recovery logs \
  -l app.kubernetes.io/name=trilio-site-recovery,app.kubernetes.io/component=api

Configuration

Configuration is managed by oslo.config. Options are declared by section in /etc/protector/protector.conf (an INI-style file). The Juju charm writes and owns this file — if you use Juju, set options via juju config rather than editing the file directly, as the charm rewrites it on every hook. For options the charm does not expose directly, use config-flags.

Required options

These must be set before the service can participate in any DR operation:

[DEFAULT] local_site

[DEFAULT]
local_site = site-primary

The name of the DR site this instance belongs to — exactly as registered with openstack dr site create. Set it differently on each site. Without it, the unit blocks (Juju) or refuses DR operations (all deployment models). There is no inference: the service does not detect which site it is on.

[database] connection

[database]
connection = mysql+pymysql://protector:ProtectorPass123@<db-host>/protector

Standard oslo.db connection string. The database name and user default to protector.

[keystone_authtoken]

[keystone_authtoken]
auth_url = https://<keystone-host>:5000
www_authenticate_uri = https://<keystone-host>:5000

[DEFAULT] — RabbitMQ transport URL

Set via oslo.messaging's standard transport_url in [DEFAULT] or through the charm relation. The engine topic defaults to protector-engine and the vhost defaults to protector.


Storage driver

[storage] storage_driver

ValueBackendProduction?
mockIn-memory mock, no hardware requiredNo
purePure Storage FlashArrayYes
drbdDRBD software replicationIn development, not for production
[storage]
storage_driver = mock

For Pure Storage, also set [DEFAULT] use_mock_storage = false and [DEFAULT] use_mock_cinder = false.


Pure Storage driver options ([pure_storage])

Required when storage_driver = pure. Both sites can use an identical [pure_storage] section — the driver resolves which array to talk to by site name, not by node position. The invariant is the pairing: primary_site_name must name the site whose array is at primary_management_ip.

[pure_storage]
primary_management_ip = 10.0.1.100
primary_api_token = <token>
secondary_management_ip = 10.0.2.100
secondary_api_token = <token>
primary_site_name = site-primary
secondary_site_name = site-dr
verify_ssl = false
default_rpo_seconds = 900
snapshot_interval_seconds = 300
OptionTypeDefaultPurpose
primary_management_ipstring—Management IP or hostname of the primary FlashArray
primary_api_tokenstring—REST API token for the primary FlashArray
secondary_management_ipstring—Management IP or hostname of the secondary FlashArray
secondary_api_tokenstring—REST API token for the secondary FlashArray
primary_site_namestring—DR site name whose volumes live on the primary array
secondary_site_namestring—DR site name whose volumes live on the secondary array
verify_sslbooleanfalseVerify TLS certificates presented by the FlashArrays
default_rpo_secondsinteger900Default RPO target in seconds for async replication
snapshot_interval_secondsinteger300Replication snapshot interval in seconds
additional_arrayslist""Names of additional array-pair sections (e.g., array-pair-2); each named section [pure_array:<name>] carries its own management IPs, API tokens, site names, and cinder_backend_names

Warning: Swapping primary_site_name and secondary_site_name causes the driver to read replication state from the wrong array during failover, reporting volumes as missing or destroyed. The driver raises StorageSiteMappingUnknown rather than guessing when it cannot match the protection group's primary site to a configured name.


API options ([api])

OptionTypeDefaultPurpose
bind_hoststring0.0.0.0Address the API listens on
bind_portinteger8788TCP port the API listens on
workersinteger4Number of gunicorn workers

Engine options ([engine])

OptionTypeDefaultPurpose
topicstringprotector-engineoslo.messaging RPC topic
cross_site_timeoutinteger30Timeout in seconds for cross-site API calls
auto_create_security_groupsbooleanfalseCreate missing security groups on the DR site during recovery
recovery_point_interval_minutesinteger0Interval between scheduled recovery point snapshots (0 = disabled)
recovery_point_max_countinteger20Maximum on-demand recovery point snapshots retained per group
recovery_point_ttl_hoursinteger24Age at which recovery point snapshots are deleted

Service credentials ([service_credentials])

OptionTypeDefaultPurpose
default_trust_rolesstringmember,dr_operatorRoles delegated by every per-tenant Keystone trust. Both roles must exist in Keystone on both sites. Do not remove member — it provides Nova/Cinder access; removing it causes failover to fail at the first instance or volume call after the storage work is done.
default_service_projectstringserviceDefault project for service credentials
cafilestring—CA certificate file for TLS verification of OpenStack APIs
insecurebooleanfalseDisable TLS certificate verification

Mock storage options ([mock_storage])

Used when storage_driver = mock:

OptionTypeDefaultPurpose
storage_base_pathstring/var/lib/protector/mock_storageDirectory for mock driver state
primary_array_namestringprimary-arrayName reported for the mock primary array
secondary_array_namestringsecondary-arrayName reported for the mock secondary array

Using config-flags on Juju

The charm exposes a fixed set of config options. For any protector.conf key the charm does not model directly, use config-flags:

juju config trilio-site-recovery config-flags="engine:cross_site_timeout=60"

Format: section:key=value, comma-separated for multiple entries. Do not use config-flags to set a key the charm already renders — this causes the section to appear twice in the config file.

Do not invent charm config option names like cross-site-timeout or engine-cross-site-timeout — these do not exist and will produce an unknown-option error.


RBAC and policy

Access control uses oslo.policy. The default rules are in /etc/protector/policy.yaml. To override a rule, create or edit /etc/protector/policy.yaml:

# Allow dr_operator to list protection groups (default)
"protector:protection_groups:list": "rule:context_is_dr_operator"

The built-in rules:

  • context_is_admin: role:admin only
  • context_is_dr_operator: role:dr_operator or role:dr_site_admin or role:admin — broader than admin-only
  • context_is_site_admin: role:dr_site_admin or role:admin

dr_site_admin is cloud-wide. Grant it on the admin project to cloud operators only.


Usage

Once deployed and configured on both sites, the typical initial setup flow is:

  1. Register both sites
  2. Create a tenant mapping between the two projects
  3. Create a protection group naming both sites
  4. Add VM members to the protection group
  5. Verify replication health before a planned failover

All CLI operations use the openstack dr subcommand family from the python3-triliositerecovery package.

Registering sites

Register each site from either site (requires dr_site_admin):

# Register the primary site
openstack dr site create site-primary \
  --auth-url https://keystone.site-primary.example.com:5000 \
  --region RegionOne

# Register the DR site
openstack dr site create site-dr \
  --auth-url https://keystone.site-dr.example.com:5000 \
  --region RegionOne

Sites have no primary/secondary type at registration time. Primary and secondary roles are assigned at the protection group level and flip on failover.

Creating a tenant mapping

A tenant mapping tells the engine which project on the peer site corresponds to the local project. Without a mapping, the engine assumes the same project UUID exists on the peer site — this holds for federated Keystone but rarely for two independent clusters:

openstack dr tenant mapping create \
  --local-tenant <local-project-id> \
  --remote-site <site-dr-id> \
  --remote-tenant <remote-project-id>

Creating a protection group

Both --primary-site and --secondary-site are required. The service never infers the secondary site:

# Async replication (requires --volume-type)
openstack dr protection group create my-pg \
  --primary-site <site-primary-id> \
  --secondary-site <site-dr-id> \
  --replication-type async \
  --volume-type <replicated-volume-type-id>

# ActiveDR continuous async replication (--volume-type not required)
openstack dr protection group create my-pg-adr \
  --primary-site <site-primary-id> \
  --secondary-site <site-dr-id> \
  --replication-type active_dr

--volume-type is required for async and enforced client-side — no request is sent if it is missing. It is not required for active_dr.

Adding members

openstack dr protection group member add <pg-id> \
  --instance <vm-id> \
  --boot-order 1

All protected VMs must boot from a Cinder volume. LUKS-encrypted volumes are rejected at this step.

Checking replication health

# Check cached replication state
openstack dr replication health <pg-id>

# Probe the recovery site for network/flavor/security-group resolution
openstack dr replication health <pg-id> --probe

The --probe flag asks the recovery site whether the protection group's networks, security groups, and flavors resolve there, as the project it recovers into. It does not test the storage backend or replication lag. Run it before a planned failover window — it surfaces resource-scoping problems that only appear at recovery time.

Failing over a protection group

Run failover from the DR (secondary) site:

openstack dr failover <pg-id> \
  --failover-type planned \
  --network-mapping <src-net-id>=<dst-net-id> \
  --flavor-mapping <src-flavor-id>=<dst-flavor-id>

Note: --network-mapping and --flavor-mapping use = as the separator, never :. A colon-separated value is accepted silently by the argument parser but does not apply — the mapping is ignored and recovery falls back to resolving resources by name.

To wait for completion:

openstack dr failover <pg-id> --failover-type planned --wait

Recovered instances are created as <name>-failover. Update any external systems keyed on instance names.

Failing back

Run failback from the primary site after the primary is restored:

openstack dr failback <pg-id> --failback-type planned

Failback waits for each DR volume's Nova attachment to release before unmanaging it (up to 120 seconds per volume). A visibly slower failback when volumes are still attached is the wait working correctly, not a hang. Recovered instances are created as <name>-restored.

Test failover (non-disruptive)

# Start a test failover
openstack dr test failover <pg-id> \
  --network-mapping <src-net-id>=<dst-net-id>

# Clean up after testing
openstack dr test failover cleanup <pg-id>

Site-level failover (all groups in a project)

# Fail over every group in this project — run on the DR site
openstack dr tenant failover --failover-type planned

# Fail back every group — run on the primary site
openstack dr tenant failback --failback-type planned

Monitoring operations

DR operations are recorded with a progress percentage (0–100). There is no estimated completion time:

# List recent operations
openstack dr operation list --protection-group <pg-id>

# Show a specific operation
openstack dr operation show <operation-id>

Observability is polled, not event-driven. The service emits no oslo.messaging notifications. Use openstack dr replication health, openstack dr replication audit, openstack dr recovery point list, and operation records to monitor state.


Examples

Register a site

openstack dr site create site-primary \
  --auth-url https://keystone.site-primary.example.com:5000 \
  --region RegionOne \
  --description "Primary production site"

List registered sites

openstack dr site list

Create a tenant mapping

openstack dr tenant mapping create \
  --local-tenant a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 \
  --remote-site f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3 \
  --remote-tenant 9988776655449988776655449988776655

Create an async protection group

openstack dr protection group create production-pg \
  --primary-site a1b2c3d4-e5f6-a1b2-c3d4-e5f6a1b2c3d4 \
  --secondary-site f6e5d4c3-b2a1-f6e5-d4c3-b2a1f6e5d4c3 \
  --replication-type async \
  --volume-type replicated-ssd

Create an ActiveDR protection group

openstack dr protection group create production-pg-adr \
  --primary-site a1b2c3d4-e5f6-a1b2-c3d4-e5f6a1b2c3d4 \
  --secondary-site f6e5d4c3-b2a1-f6e5-d4c3-b2a1f6e5d4c3 \
  --replication-type active_dr

Add a VM to a protection group

openstack dr protection group member add production-pg \
  --instance 1234abcd-5678-efgh-ijkl-mnop90123456 \
  --boot-order 1

Check replication health with probe

openstack dr replication health production-pg --probe

Planned failover with network and flavor mapping

openstack dr failover production-pg \
  --failover-type planned \
  --network-mapping net-primary-id=net-dr-id \
  --flavor-mapping m1.large=m1.large-dr \
  --wait

Planned failback

openstack dr failback production-pg \
  --failback-type planned \
  --yes

Non-disruptive test failover

openstack dr test failover production-pg \
  --network-mapping net-primary-id=net-test-id \
  --instance-prefix test- \
  --wait

Clean up after test failover

openstack dr test failover cleanup production-pg

Reconcile an inconsistent protection group

Use this when a protection group is left inconsistent by an interrupted failover or failback. Works even when the primary site is unreachable:

openstack dr protection group reconcile production-pg

The reconcile operation is recorded in the operation history with operation_type=reconcile:

openstack dr operation list --protection-group production-pg --operation-type reconcile

List recovery points

openstack dr recovery point list production-pg --limit 10

Create an on-demand recovery point

openstack dr recovery point create production-pg --suffix pre-upgrade

View site-level operations

openstack dr site operation list --site <site-id> --status completed

Run the replication audit

openstack dr replication audit

Upgrade CLI on a non-charm node

sudo apt-get install --only-upgrade python3-triliositerecovery

Juju: run a database migration

juju run trilio-site-recovery-for-openstack/leader db-sync

Kolla: run a database migration

protector-manage db sync

Kolla: preflight check before crossing migration 022

protector-manage db preflight
protector-manage db sync

Python client: list protection groups

from keystoneauth1 import session as ks_session
from keystoneauth1.identity import v3 as ks_identity
from keystoneauth1 import adapter
from triliositerecovery.v1.client import Client

auth = ks_identity.Password(
    auth_url='https://keystone.example.com:5000',
    username='my-user',
    password='my-password',
    project_name='my-project',
    user_domain_name='Default',
    project_domain_name='Default',
)
sess = ks_session.Session(auth=auth)
ksa_adapter = adapter.Adapter(
    session=sess,
    service_type='protector',
    region_name='RegionOne',
)

client = Client(
    session=ksa_adapter,
    endpoint='https://protector.example.com:8788',
)

groups = client.protection_groups.list()
for pg in groups:
    print(pg['id'], pg['name'], pg['status'])

Managers return plain dicts — use pg['id'], not pg.id.

Python client: trigger a failover

# DR operations are on client.operations, not client.protection_groups
operation = client.operations.failover(
    pg_id,
    failover_type='planned',
    network_mapping={'src-net-id': 'dst-net-id'},
    flavor_mapping={'src-flavor-id': 'dst-flavor-id'},
)
print(operation['id'], operation['status'])

The resource ID is positional (pg_id). Pass mappings as dicts, not 'src=dst' strings — the = separator syntax is argparse-only.


Troubleshooting

Unit is in BLOCKED state after Juju deployment or upgrade

Symptom: juju status shows the unit blocked with a message mentioning local-site, pure-primary-site-name, or pure-secondary-site-name.

Cause: The required local-site charm config option is not set. After upgrading to charm revision 16 (1.0.24 service), the charm lands in BLOCKED until these options are configured. This is expected behaviour — the service does not infer which site it is on.

Fix:

juju config trilio-site-recovery local-site=<site-name>
# If using Pure Storage:
juju config trilio-site-recovery \
  pure-primary-site-name=<primary-site-name> \
  pure-secondary-site-name=<dr-site-name>

Failover fails with HTTP 403 "Policy doesn't allow volume_extension:volume_manage"

Symptom: Failover starts, storage replication completes, then fails with a 403 from Cinder.

Cause: The required Cinder policy relaxations are not applied on the recovery site. The service runs as a tenant through Keystone trusts and cannot call Cinder's manage/unmanage APIs without the policy override.

Fix: Apply all five policy relaxations on both sites:

{
  "volume_extension:volume_manage": "rule:admin_or_owner",
  "volume_extension:volume_unmanage": "rule:admin_or_owner",
  "volume_extension:services:index": "rule:admin_or_owner",
  "volume_extension:type_get": "rule:admin_or_owner",
  "volume_extension:type_get_all": "rule:admin_or_owner"
}

For Juju:

juju attach-resource cinder policyd-override=cinder-policy-override.zip

DR operations fail with "Trust creation failed: role not found"

Symptom: The first DR operation (failover, failback, or member add) fails with an error naming a missing Keystone role.

Cause: The dr_operator or dr_site_admin role does not exist on one or both sites. Trust creation fails immediately when a role named in default_trust_roles is absent in Keystone.

Fix: Create both roles on both sites:

openstack role create dr_operator
openstack role create dr_site_admin

Tenant mapping fails to create / cross-site calls fail with connection errors

Symptom: openstack dr tenant mapping create fails; logs show DNS resolution failures or connection timeouts for the peer site's endpoints.

Cause: The local site cannot resolve the peer site's Keystone or API hostnames. Each site's DNS resolver typically serves only its own zone.

Fix (Juju): Add static host entries via the charm:

juju config trilio-site-recovery extra-hosts="
10.80.65.60  keystone.site-dr.example.com
10.32.65.166 protector.site-dr.example.com"

Prefer fixing DNS at the resolver level for a permanent solution.

Fix (all deployments): Verify network connectivity on port 5000 (Keystone) and port 8788 (protector API) between sites.


Network mapping is silently ignored; wrong network used after failover

Symptom: Failover completes but VMs attach to the wrong network despite providing --network-mapping.

Cause: A colon (:) was used as the separator instead of equals (=). Argparse accepts the colon-separated value as a single opaque string; no error is raised but the mapping is not applied.

Fix: Use = as the separator:

--network-mapping src-net-id=dst-net-id

Pure Storage failover reports "2 of 2 volumes missing or destroyed"

Symptom: Failover fails reporting volumes missing, but the volumes exist on the array.

Cause: primary_site_name and secondary_site_name are swapped in [pure_storage]. The driver resolves which array to query by site name; swapped names cause it to read state from the wrong array. The driver raises StorageSiteMappingUnknown rather than guessing.

Fix: Ensure primary_site_name names the site whose array is at primary_management_ip, and secondary_site_name names the site whose array is at secondary_management_ip. An identical [pure_storage] section works correctly on both nodes because the driver swaps the pair based on the protection group's primary site.


Database migration fails: ENUM does not contain RECONCILE

Symptom: protector-manage db sync fails when crossing migration 022_add_reconcile_operation_type.

Cause: The dr_operations.operation_type ENUM column does not contain the uppercase member RECONCILE. On Juju, the charm's db-sync and upgrade actions run the preflight check automatically. On Kolla-Ansible and bare-metal deployments, the preflight must be run manually.

Fix:

protector-manage db preflight
protector-manage db sync

OpenShift pods not found by label selector

Symptom: oc get pods -l app=trilio-site-recovery-api returns "No resources found".

Cause: The OpenShift manifests use app.kubernetes.io/name and app.kubernetes.io/component labels, not a bare app label.

Fix: Use the correct label selector:

oc -n trilio-site-recovery get pods \
  -l app.kubernetes.io/name=trilio-site-recovery,app.kubernetes.io/component=api

oc -n trilio-site-recovery get pods \
  -l app.kubernetes.io/name=trilio-site-recovery,app.kubernetes.io/component=engine

Python client raises AttributeError on manager results

Symptom: Code like site.id raises AttributeError.

Cause: Manager methods return plain dicts, not resource objects.

Fix: Use dict access:

# Correct
for site in client.sites.list():
    print(site['id'])

# Wrong — raises AttributeError
for site in client.sites.list():
    print(site.id)

Failover or failback is slow (volumes taking long to detach)

Symptom: Failback is visibly slow; logs show waiting for volume detach.

Cause: Failback waits for each DR volume's Nova attachment to release before unmanaging it, up to 120 seconds per volume. Nova releases the Cinder attachment asynchronously after the server is deleted. This wait is intentional — without it, unmanaging an attached volume causes it to enter error_deleting and become stranded.

Action: This is expected behaviour. If the wait completes before all volumes detach, those volumes are unmanaged anyway and logged (failback is not aborted). Check Nova and Cinder logs for slow deletion on the recovery site if this is consistently triggering.