Pure Replication Technologies
Explain Pure Storage replication technologies used in Trilio Site
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.
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
replicationin 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-clientPython SDK installed on every host running theprotector_engineservice: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'andreplication_type='<in> async'configured on both sites (see Configure replicated volume types) - Network connectivity from the
protector_enginehost to the management IP of both FlashArrays (typically HTTPS/443) protector-manage db upgraderun on both sites so theactivedr_pod_nameandactivedr_remote_connectioncolumns exist in thereplication_policiestable (added in schema revision013_activedr_enum)
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.
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
| Option | Description |
|---|---|
primary_management_ip | Management IP or hostname of the FlashArray at the primary OpenStack site |
primary_api_token | API token for the service account on the primary array |
secondary_management_ip | Management IP or hostname of the FlashArray at the secondary OpenStack site |
secondary_api_token | API token for the service account on the secondary array |
Optional options (with defaults)
| Option | Default | Description |
|---|---|---|
verify_ssl | False | Whether to verify SSL certificates when connecting to the FlashArray management API. Set to True in production environments with valid certs |
snapshot_interval_seconds | 300 | How 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_hours | 24 | Keep every snapshot for this many hours before the per-day retention policy takes over |
retention_per_day | 24 | After the retention_all_for_hours window, retain this many snapshots per calendar day |
retention_days | 7 | Total number of days to keep snapshots. Recovery points older than this are automatically eradicated on the array |
default_rpo_seconds | 900 | The 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_count | 20 | Maximum number of on-demand recovery points returned by the health API |
recovery_point_ttl_hours | 24 | Automatically 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.
| Value | Pure technology | RPO | Minimum Purity version | Use when |
|---|---|---|---|---|
ASYNC | Protection Group scheduled snapshots | Minutes (= snapshot_interval_seconds) | Any supported Purity//FA | Workloads can tolerate minutes of data loss; simpler setup |
ACTIVE_DR | Pod 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_typetoSYNC; 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:
| Field | Description |
|---|---|
activedr_pod_name | Base name for the pod pair. The engine appends -src (primary array) and -tgt (secondary array) automatically |
activedr_remote_connection | The 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.
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:
- Creates a Pure Storage Protection Group on the primary FlashArray with the name derived from the Protection Group ID
- Sets the replication target to the secondary FlashArray
- Configures the snapshot and retention schedule using the values from
protector.conf - 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:
- Verifies that a replication array-connection exists between the two FlashArrays
- Creates a source pod (
<pod-name>-src) on the primary FlashArray in the promoted (writable) state - Creates a target pod (
<pod-name>-tgt) on the secondary FlashArray and demotes it (read-only) - Creates a pod replica link from the source pod to the target pod, using the configured (or auto-detected) remote connection name
- Polls the replica link until it reaches
replicatingstatus (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-nameand--activedr-remote-connectionarguments are required foractive_drProtection Groups. Consultopenstack dr protection group create --helpfor 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>
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"
}
}
}
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.