Troubleshooting
Common API errors and debugging
This runbook helps you diagnose and resolve common API errors, service failures, and replication issues in Trilio Site Recovery for OpenStack, and describes when and how to collect a support bundle for escalation.
This runbook covers:
- Interpreting HTTP error codes returned by protector-api
- Diagnosing service health using systemd, logs, and the CLI
- Resolving common errors: authentication failures, member-add rejections, failover pre-check failures, and cross-site connectivity problems
- Collecting a support bundle from a Juju charm deployment
- Escalation guidance
This runbook does not cover:
- Initial deployment or charm configuration (see the Installation guide)
- Pure Storage FlashArray array-level troubleshooting
- OpenStack Nova, Cinder, or Neutron administration
- Non-Juju deployment paths (Kolla and OpenShift are internal development tools and are not supported customer-facing deployments)
Before using this runbook:
- The Trilio Site Recovery Juju charm (
trilio-site-recovery) is deployed and related on both sites - You have SSH access to the Juju controller and can run
juju ssh - The
openstackCLI with thetriliositerecoveryplugin is installed viapip install python3-triliositerecovery(not the snap — the openstackclient snap is incompatible with this plugin) - You have sourced a valid OpenRC file for the affected project
- You know the protection group ID or name for the affected workload
- Both sites are running Ubuntu Jammy or Noble with the
trilio-site-recoveryDebian package installed
Version note: The maximum supported API microversion is 1.1. Check
OpenStack-API-Versionheaders in error responses to confirm the version negotiated.
Work through the sections below in order. Each section addresses a distinct failure class.
1. Confirm service health on both sites
On each site's Juju unit, check that both systemd services are running:
juju ssh trilio-site-recovery/leader \
"sudo systemctl status protector-api protector-engine --no-pager"
Expected: both units show Active: active (running). If either is failed or inactive, restart it:
juju ssh trilio-site-recovery/leader \
"sudo systemctl restart protector-api protector-engine"
To verify the API process is accepting connections (no authentication required for version discovery):
curl -s http://<site-ip>:8788/
A healthy 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://<site-ip>:8788/v1/"}]
}
]
}
If you receive a connection refused or timeout, the API process is not listening — proceed to step 2.
2. Read the service logs
Application logs are written to /var/log/protector/ on each unit. Retrieve them with:
juju ssh trilio-site-recovery/leader \
"sudo journalctl -u protector-api --since '1h ago' --no-pager"
juju ssh trilio-site-recovery/leader \
"sudo journalctl -u protector-engine --since '1h ago' --no-pager"
For older log history (up to 14 days of rotated files):
juju ssh trilio-site-recovery/leader \
"sudo sh -c 'for f in /var/log/protector/*.log*; do [ -f \"\$f\" ] || continue; echo \"===== \$f ====="; zcat -f \"\$f\" 2>/dev/null; echo; done'"
Search for the Python exception class name from the error response (for example, ProtectionGroupNotFound, VolumeTypeNotReplicated) to jump directly to the relevant traceback.
3. Interpret HTTP error codes
Every error response from protector-api includes a type field (the exception class name), a code, and a message. Use the type field to identify the exact error:
{"type": "VolumeTypeNotReplicated", "code": 422, "message": "Volume type pure-local does not support replication"}
Exception: Microversion rejection errors from the version middleware return only
codeandmessage, withouttype.
Common errors and remediation
HTTP 400 — Bad request
type | Cause | Fix |
|---|---|---|
InvalidAction | Action body sent to POST /v1/{tenant_id}/protection-groups/{pg_id}/action uses an unrecognised action name | Use failover, failback, or test_failover exactly as documented |
InvalidInput | A required field is missing or a value is malformed | Check the request body and re-read --help for the relevant CLI command |
InvalidState | The protection group is in a state that does not allow the requested action | Check current PG status with openstack dr protection group show <pg-id> and wait for any in-progress operation to complete |
InvalidExecutionSite | The operation was sent to the wrong site | Issue the command from the site that currently holds the primary role for this protection group |
ReplicationTypeMismatch | Volume replication type does not match the PG's configured type | Ensure all volumes use a type matching the PG's replication_type (async or active_dr) |
ValidationError / InvalidParameters | Parameter validation failure | Review the flagged field in the error message |
| (microversion rejection) | OpenStack-API-Version: protector <x.x> value is outside 1.0–1.1 or malformed | Use 1.0 or 1.1; omitting the header entirely also works (defaults to 1.0) |
HTTP 401 — Unauthorised
type | Cause | Fix |
|---|---|---|
Unauthorized | The X-Auth-Token is missing, expired, or invalid | Re-authenticate with Keystone on port 5000 and retry with a fresh token |
SiteAuthenticationFailed | The engine could not authenticate against the remote site's Keystone | Verify service credentials with openstack dr site set <site> --service-username ... --service-password ... and confirm the protector service account exists on the remote site |
TrustAuthFailed | Keystone trust authentication failed for a cross-site operation | Confirm that dr_operator and member roles exist on both sites and are granted to the tenant's project; the service never creates roles — missing roles stop trust creation with a clear message naming the absent role |
HTTP 403 — Forbidden
type | Cause | Fix |
|---|---|---|
Forbidden | The authenticated user lacks the required role (dr_operator or dr_site_admin) | Grant member and dr_operator on the tenant's project on both sites; grant dr_site_admin only for site-level administrative operations |
ProtectionGroupFailedOver | An operation requiring the PG to be active was attempted on a failed-over PG | Fail back first, or use --force where the command supports it |
ProtectionGroupNotOnPrimarySite | The command was issued from the secondary site when it must be issued from the primary | Re-issue from the primary site |
HTTP 404 — Not found
type | Cause | Fix |
|---|---|---|
ProtectionGroupNotFound | The protection group ID or name does not exist in this site's database | Run openstack dr protection group list to confirm the correct ID; check that you are authenticated against the correct site |
OperationNotFound | The operation ID does not exist | Operations are site-local; query the site where the operation originated using openstack dr operation show <id> |
SiteNotFound | The site ID passed to a site endpoint does not exist | List registered sites with openstack dr site list |
MemberNotFound | The member ID does not exist in this PG | Run openstack dr protection group member list <pg> to confirm |
TenantMappingNotFound | No tenant mapping exists for this project pairing | Create one with openstack dr tenant mapping create |
RecoveryPointNotFound | The recovery point ID passed to failover or test failover does not exist on the secondary site | List available points with openstack dr recovery point list <pg> |
| (generic 404 on versioned URL) | The URL is missing the /v1/ segment, e.g. /{tenant_id}/sites instead of /v1/{tenant_id}/sites | All resource URLs require the /v1/{tenant_id}/ prefix |
HTTP 409 — Conflict
type | Cause | Fix |
|---|---|---|
SiteAlreadyExists | A site with that name is already registered | Use a unique name, or update the existing site with openstack dr site set |
InstanceAlreadyProtected | The VM is already a member of another protection group | Remove it from the existing PG first with openstack dr protection group member remove |
TenantMappingAlreadyExists | A mapping for this project pair already exists | Show the existing mapping with openstack dr tenant mapping list |
VolumeAlreadyInCG | The volume is already in a Cinder Consistency Group | Check for duplicate protection group membership |
HTTP 422 — Unprocessable entity
These errors indicate a DR eligibility failure. They are the most common errors when adding members or triggering failover.
type | Cause | Fix |
|---|---|---|
InstanceNotCinderBoot | The VM boots from ephemeral storage, not a Cinder volume | All protected VMs must boot from a Cinder-backed volume. Re-create the VM from a Cinder bootable volume |
VolumeTypeNotReplicated | A volume attached to the VM uses a Cinder volume type without replication_enabled='<is> True' | Create or modify a volume type with the extra-spec replication_enabled='<is> True' (and optionally replication_type='<in> async'), then re-type the volume with cinder retype |
FailoverNotReady | Pre-flight checks for failover failed | Run openstack dr replication health <pg-id> and review the failover_validation.blockers list in the response |
PreflightValidationFailed | One or more preflight validation checks failed | Same as above — inspect the health report for specifics |
NoVolumesFound / NoBootableVolumeFound | The VM has no attached Cinder volumes or no bootable volume | Attach a Cinder volume and ensure it is marked bootable |
ReplicationNotHealthy | The storage replication link is degraded or broken | Check the Pure Storage FlashArray replication connection between sites; run openstack dr replication health <pg-id> for details |
TenantValidationFailed | The remote tenant mapping could not be validated | Run openstack dr tenant mapping validate <mapping-id> and confirm the remote project exists on the secondary site |
Pre-checking member eligibility without side effects:
Before running openstack dr protection group member add, validate the instance:
openstack dr protection group member validate <pg-id> --instance <instance-id>
This returns HTTP 200 with a structured errors array listing any eligibility problems, with no state changes.
HTTP 500 — Internal server error
These indicate a failure inside the engine or storage layer. Collect logs (step 2) and look for the exception class name.
Common 500 errors:
type | Likely cause |
|---|---|
QuiesceFailure | Nova could not quiesce the VM during planned failover — check Nova agent (qemu-guest-agent) on the VM |
SnapshotReplicationTimeout | The Pure Storage snapshot did not replicate within the timeout — check inter-site bandwidth and FlashArray replication status |
InstanceCreationFailed / InstanceCreationTimeout | Nova failed to create the replica VM on the secondary site — check Nova and Cinder on the secondary site |
MetadataSyncFailed | Cross-site metadata synchronisation failed — check port 8788 connectivity between sites (must be bidirectional) |
ReplicaCreationFailed | The storage driver could not create a replica — check FlashArray credentials and network connectivity to the array |
HTTP 503 — Service unavailable
type | Cause | Fix |
|---|---|---|
SiteUnreachable / RemoteSiteUnreachable | The engine cannot reach the remote site's protector-api on port 8788 | Verify bidirectional TCP connectivity on port 8788 between sites; cross-site metadata sync requires both directions |
SiteNotHealthy | The remote site's API returned an unhealthy status | Check the remote site's service health (step 1) |
4. Diagnose cross-site connectivity failures
Cross-site connectivity failures are a common cause of SiteUnreachable, RemoteSiteUnreachable, MetadataSyncFailed, and trust authentication errors. Port 8788 must be open bidirectionally between both sites.
Verify connectivity from each site to the other:
# From site 1, reach site 2
curl -s http://<site-2-ip>:8788/
# From site 2, reach site 1
curl -s http://<site-1-ip>:8788/
Both must return the versions JSON shown in step 1. A one-way failure leaves tenant mappings created locally but unsynced to the remote site, causing validation failures on subsequent cross-site operations.
Also verify Keystone (port 5000), Nova (port 8774), and Cinder (port 8776) are reachable from each site to the other.
If your environment uses TLS and certificate verification fails, set cafile in protector.conf under [service_credentials] to a PEM-encoded CA bundle, or (development/test environments only) set insecure = true. In a Juju deployment, set these via juju config:
juju config trilio-site-recovery config-flags="service_credentials:cafile=/etc/ssl/certs/ca-certificates.crt"
In MAAS environments, asymmetric network space bindings present as broken TLS certificate verification on cross-site API calls — ensure Juju network space bindings are symmetric between sites.
5. Diagnose authentication and trust failures
If operations fail with TrustAuthFailed or SiteAuthenticationFailed:
-
Confirm the
dr_operatorandmemberKeystone roles exist on both sites:openstack role list | grep -E 'dr_operator|member'If either role is missing, create it:
openstack role create dr_operatorThe service never creates roles — trust creation fails immediately, naming the missing role.
-
Confirm the
protectorservice account hasmemberanddr_operatoron each DR-enabled project on each site:openstack role assignment list --user protector --project <project-name> -
Confirm tenant users have
memberanddr_operatorgranted on their project:openstack role assignment list --user <user> --project <project-name> -
Validate the site's service credentials against the remote Keystone:
openstack dr site set <site-id> \ --service-username protector \ --service-password <password> \ --service-project-name serviceThe API validates these credentials against the remote Keystone at save time and returns
SiteAuthenticationFailedif they are incorrect.
6. Diagnose replication health failures
When failover is blocked by FailoverNotReady or ReplicationNotHealthy, inspect the replication health report:
openstack dr replication health <pg-id>
The response nests fields under replication_health. The programmatic readiness check is:
replication_health.failover_validation.can_failover
If can_failover is false, inspect failover_validation.blockers for the specific reason. Common blockers:
- Lag too high — the secondary is behind the RPO target. Wait for the next replication cycle and retry, or accept data loss with
--failover-type unplanned. - No recovery points — the replication has not completed a successful cycle yet. Wait for the Pure Storage Protection Group to complete a snapshot cycle.
- Replication link down — check FlashArray connectivity between arrays.
You can also query the replication health directly via the API:
curl -s \
-H "X-Auth-Token: $OS_TOKEN" \
http://<site-ip>:8788/v1/<tenant-id>/protection-groups/<pg-id>/replication-health
7. Recover a protection group stuck in an intermediate state
If an operation was interrupted (for example, by a service restart during failover), the protection group may be stuck in a non-terminal state such as failing_over or failing_back.
Check current status:
openstack dr protection group show <pg-id>
Check for any running operations:
openstack dr operation list --protection-group <pg-id> --status running
Cancel a stuck in-progress operation:
openstack dr operation cancel <operation-id>
If the PG remains inconsistent after cancellation, use the reconcile command to detect and repair the interrupted state:
openstack dr protection group reconcile <pg-id>
If the PG status is incorrect and you need to force a reset, use:
# Reset to active (primary is operational)
openstack dr protection group set <pg-id> --status active
# Reset to failed_over (secondary is operational)
openstack dr protection group set <pg-id> --status failed_over
# Reset to error to mark for investigation
openstack dr protection group set <pg-id> --status error
Only use manual status resets when you have confirmed the actual storage and VM state independently — setting an incorrect status can cause further inconsistency.
8. Diagnose database connectivity failures
If the API returns 500 on all requests and the logs show database connection errors:
-
In a Juju deployment the database and user are created automatically via the
shared-dbrelation — no manual creation is required. Check that the relation is healthy:juju status trilio-site-recovery --format yaml | grep -A5 'relations' -
Confirm the charm-rendered configuration is correct. Do not edit
/etc/protector/protector.confdirectly — the charm overwrites it on everyconfig-changedevent. Usejuju configto set options:juju config trilio-site-recovery -
Run the charm health-check action to confirm database reachability:
# Juju 3.x juju run trilio-site-recovery/leader health-check # Juju 2.9 juju run-action trilio-site-recovery/leader health-check --wait
9. Diagnose storage driver misconfiguration
If failover or member-add operations fail with PureStorageError, PureConnectionError, or StorageDriverConfigError:
-
Confirm the storage driver is set to
pure(not the defaultmock):juju config trilio-site-recovery | grep storage-driverThe default is
mock, which is suitable for development only. Set it for production:juju config trilio-site-recovery storage-driver=pure -
Confirm the FlashArray management IP and API token are set. In a Juju deployment, use hyphenated config key names:
juju config trilio-site-recovery pure-primary-management-ip -
Confirm
verify_sslis set correctly for your FlashArray certificate. If using a private CA, provide it viaconfig-flags:juju config trilio-site-recovery \ config-flags="pure_storage:verify_ssl=true"
Config key format reminder: Charm configuration options use hyphens (e.g.,
pure-primary-management-ip). Config-flags overrides use colon separators:section:key=value. A dot separator in config-flags fails silently.
10. Collect a support bundle
If you cannot resolve the issue with the steps above, collect a support bundle before escalating. The bundle script collects logs, configuration (with credentials redacted), database state, and DR state from all units of the Juju application:
bash scripts/collect-support-bundle.sh \
--app trilio-site-recovery \
--since 8h \
--pg <protection-group-id> \
--openrc ~/openrc.sh \
--output ~/support-bundle
Options:
| Flag | Default | Description |
|---|---|---|
--app | trilio-site-recovery | Juju application name |
--since | 4h | How far back to collect journal logs |
--pg | (none) | Protection group ID to capture state for |
--openrc | (skip CLI) | OpenRC file for CLI-based captures |
--output | ./support-bundle-TIMESTAMP | Output directory |
The bundle includes (with credentials redacted from all output):
- Application logs from
/var/log/protector/including rotated history - systemd journal for
protector-apiandprotector-engine - Charm hook logs
- Juju status (application and model)
- Charm configuration
- Database state: non-terminal protection groups, running operations, table row counts
- DR state: site list, operation list, protection group status, replication health
- Systemd service status and package versions
- Network connectivity checks (listening sockets, DNS, resolver)
- Disk, memory, and OOM evidence
After applying a fix, confirm resolution with these checks:
-
Service health: Both systemd units are active:
juju ssh trilio-site-recovery/leader \ "sudo systemctl is-active protector-api protector-engine"Expected:
activefor both. -
API responding: Version discovery returns HTTP 200:
curl -s http://<site-ip>:8788/Expected: JSON with a
versionsarray containing"status": "CURRENT". -
Protection group health: The PG is in a terminal state and replication is healthy:
openstack dr protection group show <pg-id> openstack dr replication health <pg-id>Expected: PG
statusisactiveorfailed_over;replication_health.failover_validation.can_failoveristrue. -
No stuck operations: No operations are in
runningstate for more than the expected duration:openstack dr operation list --protection-group <pg-id> --status runningExpected: empty list, or operations that are actively progressing.
-
Cross-site connectivity: Both sites can reach each other on port 8788:
curl -s http://<site-1-ip>:8788/ curl -s http://<site-2-ip>:8788/Both must return the
versionsJSON.
The troubleshooting steps in this runbook are primarily diagnostic and read-only. The actions that do change state are:
-
Service restarts (
systemctl restart protector-api protector-engine): Restarting the services is safe and idempotent. Any in-progress operation will be left in an interrupted state; useopenstack dr operation cancel <id>followed byopenstack dr protection group reconcile <pg-id>to recover. -
Manual protection group status reset (
openstack dr protection group set <pg-id> --status active|failed_over|error): This is a low-level override. If you set an incorrect status, reset it to the correct value using the same command. The allowed values areactive,failed_over, anderrorexactly. -
Site credential update (
openstack dr site set ...): If you set incorrect credentials, update them again with the correct values. The API validates credentials against the remote Keystone at save time. -
Juju config changes (
juju config trilio-site-recovery <key>=<value>): Revert any unintentional config change by setting the key back to its previous value. Do not edit/etc/protector/protector.confdirectly — the charm overwrites it. -
Operation cancellation (
openstack dr operation cancel <id>): Cancellation cannot be undone. After cancelling, useopenstack dr protection group reconcile <pg-id>to restore consistency, then re-trigger the intended operation.
If this runbook does not resolve the issue, escalate to Trilio support at https://www.trilio.io with the following information:
-
Support bundle collected with
collect-support-bundle.sh(see step 10). The bundle includes logs, configuration, and state with credentials automatically redacted. -
Error details:
- The exact error
type,code, andmessagefrom the API response - The CLI command or API request that triggered the error
- The protection group ID and both site IDs
- The operation ID if an async operation failed (from
openstack dr operation show <id>)
- The exact error
-
Environment details:
- Output of
juju status trilio-site-recovery --format yaml - Package versions from each unit:
dpkg -l trilio-site-recovery python3-triliositerecovery - OpenStack version on each site
- Pure Storage Purity//FA version on each array
- Output of
-
Replication health report:
openstack dr replication health <pg-id> -
Timeline: When the issue first appeared, what changed around that time (deployments, network changes, array maintenance), and whether the issue is reproducible.
Provide all of the above in your initial support request to avoid round-trips.