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

Pure Replication Technologies

Explain Pure Storage replication technologies used in Trilio Site


Overview

Trilio Site Recovery for OpenStack uses Pure Storage FlashArray as its replication backend to keep your Cinder volumes synchronised between a primary and a secondary OpenStack site. This page explains the two replication technologies the service supports — periodic async replication (Protection Group snapshot-based) and ActiveDR (pod replica-link continuous async) — describes how each maps to a Protection Group's replication_type, and walks you through configuring and operating both modes. Understanding which technology fits your RPO requirements before you create a Protection Group will save you significant rework later, because the replication type cannot be changed after creation.


Prerequisites

Before configuring Pure replication for Trilio Site Recovery, make sure you have:

  • Two Pure Storage FlashArray arrays — one at each OpenStack site — running Purity//FA 6.0 or later (required for ActiveDR; periodic async works on earlier Purity versions but 6.0+ is recommended for both)
  • A replication array-connection already established between the two FlashArrays (type replication in the Purity GUI under Settings → Network → Connected Arrays). This connection must exist before either replication mode can be configured; Trilio Site Recovery does not create it
  • API tokens for a service account on each array with sufficient privileges to manage protection groups, pods, replica links, and snapshots
  • The py-pure-client Python SDK installed on every host running the protector_engine service: pip install py-pure-client
  • Trilio Site Recovery deployed and both OpenStack sites registered (openstack dr site create)
  • Cinder volume types with replication_enabled='<is> True' and replication_type='<in> async' configured on both sites (see Configure replicated volume types)
  • Network connectivity from the protector_engine host to the management IP of both FlashArrays (typically HTTPS/443)
  • protector-manage db upgrade run on both sites so the activedr_pod_name and activedr_remote_connection columns exist in the replication_policies table (added in schema revision 013_activedr_enum)

Installation

The Pure Storage driver ships as part of the protector-engine package. The only additional installation step is the py-pure-client SDK.

Step 1 — Install the Pure Storage SDK on every engine node

Run this on each host (or container) where protector_engine is running:

pip install py-pure-client

Verify the import succeeds:

python -c "from pypureclient import flasharray; print('py-pure-client OK')"

Expected output:

py-pure-client OK

If the import fails, the engine will log py-pure-client SDK not installed. Pure Storage operations will fail. and all storage driver calls will raise a StorageDriverConfigError.

Step 2 — Run the database migration on both sites

The ActiveDR schema columns and the ACTIVE_DR enum value are added by Alembic revisions 012_add_activedr_columns and 013_activedr_enum. If you are upgrading from an earlier release, apply the migrations before starting the engine:

protector-manage db upgrade

Run this command on both the primary and secondary sites. There is no db sync subcommand; db upgrade is the correct form.

Step 3 — Verify the replication array-connection on the FlashArray

Before creating any Protection Group with async or ActiveDR replication, confirm that the arrays are connected. From a host with the py-pure-client SDK available, or from the Purity CLI on FA1:

python - <<'EOF'
import os
from pypureclient import flasharray
fa1 = flasharray.Client(target="<FA1_MGMT_IP>", api_token=os.environ["FA1_TOKEN"])
conns = fa1.get_array_connections()
for c in (conns.items or []):
    print(c.name, getattr(c, 'type', None))
EOF

Expected output (the exact name will vary):

fa2-array  replication

If the connection type is missing or shows sync-replication, the arrays are not configured correctly for asynchronous or ActiveDR replication. Establish the connection through the Purity GUI before proceeding.


Configuration

Replication technology settings are supplied to the Pure Storage driver through the [replication_policy] section in protector.conf on each engine node. The driver selects behaviour based on the replication_type stored on each Protection Group rather than a single global setting, so the configuration primarily provides array credentials and default schedule parameters.

Required options

OptionDescription
primary_management_ipManagement IP or hostname of the FlashArray at the primary OpenStack site
primary_api_tokenAPI token for the service account on the primary array
secondary_management_ipManagement IP or hostname of the FlashArray at the secondary OpenStack site
secondary_api_tokenAPI token for the service account on the secondary array

Optional options (with defaults)

OptionDefaultDescription
verify_sslFalseWhether to verify SSL certificates when connecting to the FlashArray management API. Set to True in production environments with valid certs
snapshot_interval_seconds300How often the engine instructs the FlashArray to take and replicate a Protection Group snapshot (periodic async only). This is your effective RPO floor for async replication — the actual RPO can be higher if replication falls behind
retention_all_for_hours24Keep every snapshot for this many hours before the per-day retention policy takes over
retention_per_day24After the retention_all_for_hours window, retain this many snapshots per calendar day
retention_days7Total number of days to keep snapshots. Recovery points older than this are automatically eradicated on the array
default_rpo_seconds900The RPO target (in seconds) used when calculating RPO compliance in the replication health report. A lag above this value marks the Protection Group as non-compliant. Does not affect the actual replication schedule
recovery_point_max_count20Maximum number of on-demand recovery points returned by the health API
recovery_point_ttl_hours24Automatically eradicate on-demand recovery points older than this value

Choosing a replication type

The replication_type is set per Protection Group at creation time and stored in the protection_groups table as one of ASYNC or ACTIVE_DR.

ValuePure technologyRPOMinimum Purity versionUse when
ASYNCProtection Group scheduled snapshotsMinutes (= snapshot_interval_seconds)Any supported Purity//FAWorkloads can tolerate minutes of data loss; simpler setup
ACTIVE_DRPod replica links (ActiveDR)Near-zero (continuous async)Purity//FA 6.0+Workloads require the lowest possible data loss without full synchronous replication

Important: Synchronous replication via Pure Storage ActiveCluster is not a currently supported replication type. Do not set replication_type to SYNC; that value is reserved for a future release.

ActiveDR-specific options

When a Protection Group uses ACTIVE_DR, two additional fields are stored in the replication_policies table and must be provided when creating the Replication Policy:

FieldDescription
activedr_pod_nameBase name for the pod pair. The engine appends -src (primary array) and -tgt (secondary array) automatically
activedr_remote_connectionThe name of the replication array-connection on the primary FlashArray that points to the secondary array (the name field returned by get_array_connections)

If activedr_remote_connection is not set and only one replication-type connection exists on the array, the engine will auto-detect it. If multiple connections exist, you must specify the name explicitly to avoid ambiguity.


Usage

Periodic async replication (Protection Group snapshots)

Periodic async replication is the default and simplest mode. When you create a Protection Group with replication_type=async, the engine:

  1. Creates a Pure Storage Protection Group on the primary FlashArray with the name derived from the Protection Group ID
  2. Sets the replication target to the secondary FlashArray
  3. Configures the snapshot and retention schedule using the values from protector.conf
  4. Adds each Cinder volume's backend name to the Protection Group as members are added

Snapshots are taken and replicated automatically on the configured interval. Each replicated snapshot becomes a recovery point you can select during failover or test failover.

Creating a Protection Group with async replication:

openstack dr protection group create \
  --name my-pg \
  --primary-site <primary-site-id> \
  --secondary-site <secondary-site-id> \
  --replication-type async

Adding a VM:

openstack dr protection group member add <pg-id> --instance <nova-instance-id>

Checking replication health:

openstack dr replication health <pg-id>

The health report includes the current lag in seconds, whether the lag is within your configured default_rpo_seconds, how many recovery points are available on the secondary array, and whether the Protection Group is ready for failover.


ActiveDR replication (pod replica links)

ActiveDR provides continuous asynchronous replication via Pure Storage pod replica links. Instead of periodic snapshots, the FlashArray continuously streams writes from the source pod to the target pod, resulting in a near-zero RPO.

When you create a Protection Group with replication_type=active_dr, the engine:

  1. Verifies that a replication array-connection exists between the two FlashArrays
  2. Creates a source pod (<pod-name>-src) on the primary FlashArray in the promoted (writable) state
  3. Creates a target pod (<pod-name>-tgt) on the secondary FlashArray and demotes it (read-only)
  4. Creates a pod replica link from the source pod to the target pod, using the configured (or auto-detected) remote connection name
  5. Polls the replica link until it reaches replicating status (baseline complete)

Volumes added to the Protection Group are moved into the source pod. They are automatically replicated to the target pod via the replica link.

Creating a Protection Group with ActiveDR replication:

openstack dr protection group create \
  --name my-adr-pg \
  --primary-site <primary-site-id> \
  --secondary-site <secondary-site-id> \
  --replication-type active_dr \
  --activedr-pod-name my-adr-pg \
  --activedr-remote-connection fa2-array

Note: The --activedr-pod-name and --activedr-remote-connection arguments are required for active_dr Protection Groups. Consult openstack dr protection group create --help for the complete argument list.


Failover

For both replication types, failover is triggered the same way:

# Planned failover (graceful — shuts down VMs and takes a final snapshot)
openstack dr failover <pg-id> --failover-type planned

# Unplanned failover (immediate — uses the most recent recovery point)
openstack dr failover <pg-id> --failover-type unplanned

For async Protection Groups, planned failover triggers a final on-demand snapshot and waits for it to replicate before promoting volumes on the secondary array.

For ActiveDR Protection Groups, failover promotes the target pod (makes it writable) and demotes the source pod. The engine also cleans up artifact pods (<pod-name>.undo-demote.* and <pod-name>.relink) that Pure Storage creates during promotion changes.


Test failover

A non-disruptive test failover clones replicated volumes from a specific recovery point on the secondary site and boots isolated test VMs without touching production:

openstack dr test failover <pg-id> --auto-network

Or with explicit network mapping:

openstack dr test failover <pg-id> --network-mapping <src-net-id>:<dst-net-id>

Either --auto-network or --network-mapping is required. Clean up test resources when finished:

openstack dr test failover cleanup <pg-id>

Monitoring operation progress

All long-running DR operations (failover, failback, test failover, cleanup) are tracked as DR Operations with a unique ID and a progress percentage from 0 to 100:

openstack dr operation show <operation-id>

Examples

Example 1 — Create and verify an async Protection Group

This example registers a Protection Group using periodic async replication, adds a VM, and confirms that replication is healthy.

# 1. Create the Protection Group
openstack dr protection group create \
  --name prod-web-tier \
  --primary-site a1b2c3d4-primary \
  --secondary-site e5f6a7b8-secondary \
  --replication-type async

Expected output (abbreviated):

+--------------------+--------------------------------------+
| Field              | Value                                |
+--------------------+--------------------------------------+
| id                 | 9f3e1a2b-0000-4c5d-8e6f-111122223333 |
| name               | prod-web-tier                        |
| replication_type   | async                                |
| status             | creating                             |
+--------------------+--------------------------------------+
# 2. Add a Nova instance to the Protection Group
openstack dr protection group member add 9f3e1a2b-0000-4c5d-8e6f-111122223333 \
  --instance c9d8e7f6-nova-uuid-0001

Expected output:

+-------------+--------------------------------------+
| Field       | Value                                |
+-------------+--------------------------------------+
| operation   | 7a8b9c0d-op-uuid-0001                |
| status      | accepted                             |
+-------------+--------------------------------------+
# 3. Wait for the member-add operation to complete
openstack dr operation show 7a8b9c0d-op-uuid-0001

Expected output when complete:

+------------+--------------------------------------+
| Field      | Value                                |
+------------+--------------------------------------+
| id         | 7a8b9c0d-op-uuid-0001                |
| status     | succeeded                            |
| progress   | 100                                  |
+------------+--------------------------------------+
# 4. Check replication health (allow at least one snapshot interval to pass)
openstack dr replication health 9f3e1a2b-0000-4c5d-8e6f-111122223333

Expected output (abbreviated):

+------------------------------+---------------------------+
| Field                        | Value                     |
+------------------------------+---------------------------+
| link_status                  | replicating               |
| lag_seconds                  | 42                        |
| rpo_compliant                | True                      |
| failover_ready               | True                      |
| recovery_points_available    | 3                         |
+------------------------------+---------------------------+

Example 2 — Create an ActiveDR Protection Group

This example sets up a Protection Group using Pure Storage ActiveDR pod replica links.

# 1. Confirm the replication array-connection name on the primary FlashArray
python - <<'EOF'
import os
from pypureclient import flasharray
fa1 = flasharray.Client(target="10.21.67.50", api_token=os.environ["FA1_TOKEN"])
conns = fa1.get_array_connections()
for c in (conns.items or []):
    if getattr(c, 'type', None) == 'replication':
        print("connection name:", c.name)
EOF

Expected output:

connection name: fa2-array
# 2. Create the Protection Group with ActiveDR replication
openstack dr protection group create \
  --name db-tier-adr \
  --primary-site a1b2c3d4-primary \
  --secondary-site e5f6a7b8-secondary \
  --replication-type active_dr \
  --activedr-pod-name db-tier-adr \
  --activedr-remote-connection fa2-array

Expected output:

+--------------------+--------------------------------------+
| Field              | Value                                |
+--------------------+--------------------------------------+
| id                 | 2d4e6f80-0000-4a5b-9c7d-444455556666 |
| name               | db-tier-adr                          |
| replication_type   | active_dr                            |
| status             | creating                             |
+--------------------+--------------------------------------+

The engine creates pods db-tier-adr-src on FA1 and db-tier-adr-tgt on FA2, then establishes the replica link. Baseline replication begins immediately. You can track progress:

openstack dr replication health 2d4e6f80-0000-4a5b-9c7d-444455556666

Wait until link_status shows replicating before executing a failover.


Example 3 — Planned failover and failback (async)

# Trigger a planned failover
openstack dr failover 9f3e1a2b-0000-4c5d-8e6f-111122223333 --failover-type planned

Expected output:

+-------------+--------------------------------------+
| Field       | Value                                |
+-------------+--------------------------------------+
| operation   | b1c2d3e4-op-uuid-0002                |
| status      | accepted                             |
+-------------+--------------------------------------+
# Monitor progress
openstack dr operation show b1c2d3e4-op-uuid-0002

Once status is succeeded, workloads are running on the secondary site.

# When ready, fail back to the primary site
openstack dr failback 9f3e1a2b-0000-4c5d-8e6f-111122223333

Example 4 — Non-disruptive test failover

# Run a test failover using automatic network assignment on the secondary site
openstack dr test failover 9f3e1a2b-0000-4c5d-8e6f-111122223333 --auto-network

The engine clones volumes from the most recent recovery point, boots test VMs with a test- prefix on an isolated network, and leaves production completely untouched.

# Clean up test resources when validation is complete
openstack dr test failover cleanup 9f3e1a2b-0000-4c5d-8e6f-111122223333

Example 5 — Query replication health via REST API

curl -s -X GET \
  -H "X-Auth-Token: <keystone-token>" \
  -H "OpenStack-API-Version: protector 1.1" \
  http://<protector-api-host>:8788/v1/<tenant-id>/protection-groups/9f3e1a2b-0000-4c5d-8e6f-111122223333/replication-health

Expected response (abbreviated):

{
  "replication_health": {
    "storage_group_name": "tsr-pg-9f3e1a2b",
    "replication_type": "async",
    "link": {
      "status": "replicating",
      "connected": true
    },
    "lag": {
      "lag_seconds": 42,
      "rpo_compliant": true,
      "rpo_target_seconds": 900
    },
    "failover_ready": true,
    "recovery_points": {
      "total_count": 6,
      "newest_replicated": "2026-07-01T12:34:00Z"
    }
  }
}

Troubleshooting

Use the following format for each issue: Symptom, Likely cause, Fix.


Symptom: protector_engine fails to start with StorageDriverConfigError: Pure Storage backend requires py-pure-client SDK.

Likely cause: The py-pure-client package is not installed in the Python environment used by the engine.

Fix: Install the SDK in the correct environment:

pip install py-pure-client

If running in a container, rebuild the image with py-pure-client included, or install it as a post-start command. Restart the protector_engine service after installation.


Symptom: Protection Group creation fails with No replication array-connection found between arrays.

Likely cause: The FlashArray-to-FlashArray replication array-connection has not been established, or the connection exists but has type sync-replication instead of replication.

Fix: Log in to the Purity GUI on the primary FlashArray and navigate to Settings → Network → Connected Arrays. Establish a connection to the secondary FlashArray using a connection key. Verify the connection type is replication. ActiveDR uses the same replication-type connection as periodic async — do not use sync-replication.


Symptom: ActiveDR Protection Group creation stalls at Waiting for replica link to reach 'replicating' for more than 10 minutes.

Likely cause (a): The target pod already has an .undo-demote artifact pod from a previous demotion operation. Purity will refuse to create a new replica link until the artifact pod is eradicated (it auto-eradicates after approximately 24 hours).

Fix (a): Manually eradicate the artifact pod on FA2:

import os
from pypureclient import flasharray
fa2 = flasharray.Client(target="<FA2_MGMT_IP>", api_token=os.environ["FA2_TOKEN"])
resp = fa2.get_pods(filter="name='<pod-name>.*'")
for p in (resp.items or []):
    fa2.patch_pods(names=[p.name], pod=flasharray.PodPatch(destroyed=True))
    fa2.delete_pods(names=[p.name])
    print("Eradicated:", p.name)

Then retry the Protection Group creation.

Likely cause (b): Network connectivity between the arrays is interrupted. The replica link baseline cannot complete.

Fix (b): Verify array-to-array network connectivity. Confirm that both arrays can reach each other on the replication interface. Check Pure Storage replication connection status in the Purity GUI.


Symptom: openstack dr replication health <pg-id> shows rpo_compliant: False and lag_seconds is growing.

Likely cause: The replication schedule interval is longer than default_rpo_seconds, or the inter-array link does not have sufficient bandwidth to replicate all snapshot data within the interval.

Fix: Review snapshot_interval_seconds and default_rpo_seconds in protector.conf. If lag is consistently increasing, reduce the write workload on the protected volumes, increase the replication bandwidth between arrays, or increase snapshot_interval_seconds to reduce snapshot frequency (accepting a higher RPO). Use openstack dr replication health <pg-id> to monitor lag trend after making changes.


Symptom: Failover fails with Volume '<name>' has no secondary volume ID. It was not replicated during failover.

Likely cause: A volume was added to the Protection Group but at least one interval-complete snapshot has not yet been replicated to the secondary array. The volume exists in the Consistency Group on the primary but no replica is present on the secondary.

Fix: Check openstack dr replication health <pg-id> and confirm that recovery_points_available is greater than zero and that newest_replicated is recent. Wait for at least one complete snapshot replication cycle before retrying failover. For planned failover, the engine automatically triggers a final snapshot; ensure the inter-array link is healthy before initiating.


Symptom: protector-manage db upgrade fails with column 'activedr_pod_name' of relation 'replication_policies' already exists.

Likely cause: The migration was already applied (possibly on one site but not the other), or the migration table is out of sync.

Fix: Check the current Alembic revision:

protector-manage db current

If the output shows revision 013_activedr_enum, the schema is up to date and the error can be safely ignored. If revisions are inconsistent between sites, run db upgrade on the site that is behind. There is no db sync subcommand; always use db upgrade.


Symptom: API request returns HTTP 400 with Invalid microversion when calling the replication-health endpoint.

Likely cause: The OpenStack-API-Version header is missing, malformed, or specifies a version higher than the supported maximum of 1.1.

Fix: Ensure your request includes a valid microversion header:

OpenStack-API-Version: protector 1.1

Valid values are protector 1.0 and protector 1.1. Any other value returns HTTP 400 Bad Request.