Horizon Dashboard Plugin
Reference for horizon-plugin/protector_dashboards/protection_groups.
The Protection Groups panel in the Trilio Site Recovery Horizon dashboard provides a browser-based interface for managing the full lifecycle of Protection Groups — the core unit of disaster recovery. Through this panel, tenants can create Protection Groups, add Nova VMs as members, monitor replication health, launch failover and test failover dialogs with recovery point selection, initiate failback, and track the step-by-step progress of in-flight DR operations. Use this panel when you want a visual alternative to the openstack dr CLI for everyday DR management tasks.
The dashboard panel itself takes no configuration parameters from the user at the URL level. Individual views accept path and query parameters as follows:
| Parameter | Type | Required | Description |
|---|---|---|---|
pg_id | string (UUID or name) | Yes (detail, failover, failback, add_member, edit_member, add_mapping, test_failover, test_cleanup views) | The ID of the Protection Group the view operates on. Captured from the URL path segment (?P<pg_id>[^/]+). |
member_id | string (UUID) | Yes (edit_member view only) | The ID of the Protection Group member to edit. Captured from (?P<member_id>[^/]+). |
operation_id | string (UUID) | Yes (operation_detail view only) | The ID of a specific DR Operation to inspect. Captured from (?P<operation_id>[^/]+). |
Workflow forms within the panel expose the following user-facing fields:
| Field | Form / Workflow | Required | Description |
|---|---|---|---|
| Name | CreateProtectionGroup | Yes | A unique display name for the new Protection Group. |
| Description | CreateProtectionGroup | No | Optional free-text description. |
| Primary Site | CreateProtectionGroup | Yes | The OpenStack site where workloads normally run. |
| Secondary Site | CreateProtectionGroup | Yes | The OpenStack DR site to fail over to. |
| Replication Type | CreateProtectionGroup | No | async (periodic snapshot-based, configurable RPO) or active_dr (Pure Storage ActiveDR pod-based, near-zero RPO). Defaults to async. |
| Volume Type | CreateProtectionGroup | No | The Cinder volume type with replication_enabled='<is> True' to associate with this Protection Group. |
| Instance | AddMemberWorkflow | Yes | The Nova instance UUID to add. The instance must boot from a Cinder volume; instances using ephemeral boot disks are rejected. All attached Cinder volumes must use a volume type with replication_enabled='<is> True'. |
| Boot Order | AddMemberWorkflow / EditMemberWorkflow | No | Integer controlling startup/shutdown sequence during failover and failback. VMs sharing the same boot order value are processed in parallel; lower values complete before higher values begin. |
| Failover Type | FailoverWorkflow | Yes | planned (graceful shutdown, zero data loss) or unplanned (skips shutdown, data loss up to RPO). |
| Recovery Point | FailoverWorkflow / TestFailoverWorkflow | No | Selects a specific point-in-time snapshot on the secondary site. Useful for recovering from ransomware or accidental corruption. Defaults to the most recent recovery point. |
| Network Mapping | FailoverWorkflow / TestFailoverWorkflow | Conditional | Required unless Auto Network is selected. Maps source networks (by name) to target network UUIDs on the secondary site. The widget auto-selects a target network whose name matches the source; leave blank to match by name automatically. |
| Auto Network | TestFailoverWorkflow | Conditional | When selected, the engine automatically selects a network for test VMs on the secondary site. Either Auto Network or an explicit Network Mapping is required for test failover. |
| Flavor Mapping | FailoverWorkflow / TestFailoverWorkflow | No | Maps source flavor IDs to target flavor UUIDs on the secondary site. The widget pre-selects matching names. |
| Force | TestCleanupWorkflow | No | Forces cleanup of test failover resources even if some individual resources fail to delete. |
| Operation | TestCleanupWorkflow | No | Targets a specific test failover operation ID to clean up. Defaults to the most recent test failover for the Protection Group. |
Each view in the panel returns an HTML response rendered by the corresponding Django template. The following summarises what each view presents:
| View / URL | What is rendered |
|---|---|
IndexView (/) | A data table (ProtectionGroupsTable) listing all Protection Groups for the current project, including name, status, replication type, failover readiness (e.g. Ready (4s lag) or Not Ready), and network readiness. A progress banner is injected when a DR operation is in flight for the current session. |
DetailView (/<pg_id>/) | Four sub-tables: Members (VMs in the group, their boot order, and attached volume details), Recovery Points (available snapshots on the secondary site), Resource Mappings (network and flavor mappings stored against the group), and Operations (the 10 most recent DR operations for this group). Also renders replication health metrics — lag, RPO compliance, sync state, link status, transfer throughput, and a failover_validation block indicating whether failover can proceed. |
OperationDetailView (/operation/<operation_id>/) | Full step-by-step progress record for a single DR Operation, including status, percentage complete (0–100), and per-step outcomes. |
OperationStatusJsonView (/operation-status/) | A JSON response used by the progress banner to poll in-flight operation status without reloading the page. Returns the current status, progress, and operation_id from the session-stored pending operation. |
Workflow views (/create/, /<pg_id>/failover/, etc.) | Modal dialogs rendered as Horizon workflows. On successful submission, the panel stores the returned operation_id in the Django session and redirects to the index page, where the progress banner begins polling. |
All API-backed data is retrieved through protectormgr helpers, which call the protector-api at http://<host>:8788. The underlying API endpoints are:
GET /v1/{tenant_id}/protection-groups— index listingGET /v1/{tenant_id}/protection-groups/{id}— detail viewGET /v1/{tenant_id}/protection-groups/{id}/replication-health— health metrics embedded in detail and indexGET /v1/{tenant_id}/protection-groups/{pg_id}/members— member sub-tableGET /v1/{tenant_id}/protection-groups/{pg_id}/mappings— resource mappings sub-tableGET /v1/{tenant_id}/protection-groups/{id}/recovery-points— recovery points sub-tableGET /v1/{tenant_id}/operations(filtered bypg_id) — operations sub-tableGET /v1/{tenant_id}/operations/{id}— operation detail and banner pollingPOST /v1/{tenant_id}/protection-groups/{pg_id}/action— failover, failback, and test failover submission (returns HTTP 202 withoperation_id)POST /v1/{tenant_id}/protection-groups— Protection Group creationPOST /v1/{tenant_id}/protection-groups/{pg_id}/members— member addPUT /v1/{tenant_id}/protection-groups/{pg_id}/members/{id}— member boot-order editDELETE /v1/{tenant_id}/protection-groups/{pg_id}/members/{id}— member removal (returns HTTP 202)DELETE /v1/{tenant_id}/protection-groups/{id}— Protection Group deletion (returns HTTP 204)
The dashboard panel surfaces errors to the user through Horizon's messages and exceptions framework. The underlying protector-api errors that can appear are:
| Error | HTTP Status | When it occurs |
|---|---|---|
InstanceNotCinderBoot | 422 Unprocessable Entity | Raised at member-add time when the Nova instance does not boot from a Cinder volume (uses ephemeral storage instead). The Add VM dialog displays this as a validation error. |
VolumeTypeNotReplicated | 422 Unprocessable Entity | Raised at member-add time when one or more Cinder volumes attached to the instance use a volume type that does not have replication_enabled='<is> True' set as an extra-spec. |
InvalidVolumeType | 400 Bad Request | Raised when a volume's type mismatches the Protection Group's configured volume type. |
InvalidState | 409 Conflict | Raised when the requested action (failover, failback, test failover) is not permitted in the Protection Group's current state. |
ProtectionGroupFailedOver | 409 Conflict | Raised when attempting a member add, delete, or other modification on a Protection Group that is currently in the failed_over state. |
FailoverNotReady / PreflightValidationFailed | 422 Unprocessable Entity | Raised when a failover pre-check fails — for example, the replication link is broken or can_failover is false in the replication health report. |
InvalidExecutionSite | 400 Bad Request | Raised when the operation cannot execute on the site receiving the request — for example, initiating failback from the secondary site. |
ResourceConflict | 409 Conflict | Synthesised by the fault middleware for any database unique-constraint violation — for example, reusing the name of a recently deleted Protection Group. |
ServiceUnavailable (database) | 503 Service Unavailable | Synthesised by the fault middleware when protector-api loses connectivity to MariaDB/MySQL. Retryable; unrelated to cross-site reachability. |
| SiteUnreachable | 503 Service Unavailable | Raised when the peer site's protector-api on port 8788 is unreachable during a cross-site operation. |
| Authentication failure | 401 Unauthorized | The Horizon session token (obtained from Keystone on port 5000) has expired or is invalid. Horizon will redirect to the login page. |
Unable to retrieve protection groups. (Horizon message) | — | Displayed on the index page if the GET /v1/{tenant_id}/protection-groups call fails for any reason. The table renders empty rather than raising an unhandled exception. |
Unable to retrieve protection group "<id>". (Horizon message) | — | Displayed on the detail page if the GET /v1/{tenant_id}/protection-groups/{id} call fails; the view redirects to the index page. |
The Failover action button is suppressed (not rendered) for Protection Groups whose status is failing_over, failing_back, deleting, error, or failed_over, so the 409/422 errors for those states are rare in normal dashboard use. Similarly, the Failback button is only shown when status is failed_over and the local site matches the original primary site.
Browsing to the Protection Groups panel
The panel is registered under the Project dashboard. Navigate to:
https://<horizon-host>/project/protection_groups/
You must be logged in as a user with the dr_operator role on your project. The index page lists all Protection Groups for your project with their replication health summary.
Creating a Protection Group
-
On the index page, click Create Protection Group.
-
In the dialog that opens, complete the required fields:
Field Example value Name prod-web-tierPrimary Site london-primarySecondary Site frankfurt-drReplication Type asyncVolume Type cinder-replicated -
Click Create. The panel redirects to the index page and displays a progress banner while the Protection Group is initialised.
The equivalent CLI command is:
openstack dr protection group create prod-web-tier \
--primary-site london-primary \
--secondary-site frankfurt-dr \
--replication-type async \
--volume-type cinder-replicated
Adding a VM to a Protection Group
- From the index page, click the name of your Protection Group to open the detail page.
- Click Add VM.
- Enter the Nova instance UUID in the Instance field.
- Optionally set a Boot Order integer (e.g.
1for database VMs that must start first,2for application VMs). - Click Add.
If the instance uses ephemeral storage instead of a Cinder-backed boot volume, the dialog returns an InstanceNotCinderBoot error. If any attached volume lacks replication_enabled='<is> True', it returns a VolumeTypeNotReplicated error. Correct the instance or volume configuration before retrying.
The equivalent CLI command is:
openstack dr protection group member add prod-web-tier \
--instance a1b2c3d4-e5f6-7890-abcd-ef1234567890
Monitoring replication health on the detail page
Open the detail page for a Protection Group. The Replication Health section displays:
- Failover Ready:
YesorNo, derived fromreplication_health.failover_validation.can_failover. - Lag: Current replication lag in seconds (
replication_health.health_report.lag.lag_seconds). - RPO Compliant: Whether lag is within the configured RPO target.
- Link Status: Connected/disconnected and direction (
replication_health.health_report.link). - Transfer Throughput: Current replication throughput in MB/s.
- Recovery Points: Count of available point-in-time snapshots on the secondary site.
The underlying API call is:
GET /v1/{tenant_id}/protection-groups/{id}/replication-health
Example response structure (relevant excerpt):
{
"replication_health": {
"protection_group_id": "pg-uuid",
"protection_group_name": "prod-web-tier",
"failover_validation": {
"can_failover": true,
"warnings": [],
"blockers": [],
"message": "Protection group is ready for failover"
},
"health_report": {
"failover_ready": true,
"failover_ready_reason": "Replication link healthy",
"data_at_risk_seconds": 42,
"replication_type": "async",
"link": {
"connected": true,
"status": "healthy",
"direction": "primary_to_secondary"
},
"lag": {
"lag_seconds": 42,
"lag_bytes": null,
"rpo_compliant": true,
"rpo_target_seconds": 900,
"trend": "stable"
},
"recovery_points": {
"total_count": 8,
"replicated_count": 8,
"snapshots_missed_24h": 0
},
"transfer": {
"throughput_bytes_per_sec": 5242880
}
}
}
}
Running a test failover
- From the Protection Group detail page, click Test Failover.
- In the workflow dialog:
- Select a Recovery Point (or leave blank for the most recent).
- Choose Auto Network to let the engine select a network automatically, or configure Network Mapping to map each source network to a target network on the secondary site.
- Optionally configure Flavor Mapping and Availability Zone.
- Click Run Test Failover.
The panel stores the returned operation_id in the session and displays a progress banner. Test VMs are created with a test- prefix on an isolated network on the secondary site. Production workloads are not affected.
To clean up test resources after validation, click Test Cleanup from the detail page. Check Force if you want cleanup to proceed even if some resources fail to delete.
The equivalent CLI commands are:
# Initiate test failover
openstack dr test failover prod-web-tier --auto-network
# Clean up after testing
openstack dr test failover cleanup prod-web-tier
Executing a planned failover
- Navigate to the Protection Groups panel on the secondary (DR) site. The Failover button is only rendered on the secondary site when the Protection Group status is
active. - Click Failover next to the Protection Group.
- Select Failover Type:
planned(gracefully shuts down VMs and takes a final snapshot for zero data loss) orunplanned(skips shutdown; data loss up to the RPO applies). - Optionally select a Recovery Point.
- Click Execute Failover.
The operation is submitted as:
POST /v1/{tenant_id}/protection-groups/{pg_id}/action
With an action body such as:
{
"failover": {
"failover_type": "planned"
}
}
The API returns HTTP 202 with a body including operation_id and initial status of pending. The Horizon progress banner polls GET /v1/{tenant_id}/operations/{operation_id} until status reaches completed or failed.
Executing failback
- Navigate to the Protection Groups panel on the original primary site. The Failback button is only rendered on the original primary site when the Protection Group status is
failed_over. - Click Failback.
- Confirm the operation in the dialog.
- Click Execute Failback.
The equivalent CLI command is:
openstack dr failback prod-web-tier
Polling operation progress via the JSON endpoint
The index page injects a lightweight polling script that calls:
GET /project/protection_groups/operation-status/
This view reads the dr_pending_operation key from the Django session and fetches the current operation status from:
GET /v1/{tenant_id}/operations/{operation_id}
When status reaches completed, failed, or cancelled, the session key is cleared and the banner is dismissed. You can also inspect any operation directly at:
https://<horizon-host>/project/protection_groups/operation/<operation_id>/
Panel registration and visibility
The panel is registered under the Project dashboard (openstack_dashboard.dashboards.project) with the slug protection_groups. It becomes visible to users whose Keystone token carries the dr_operator role on their project. Users without this role will not see the panel in the sidebar. Both dr_operator and dr_site_admin roles must be pre-created on both sites before any cross-site operation can succeed — the service looks up roles rather than creating them, and trust creation fails immediately if either role is absent.
Site-aware button visibility
The Failover and Failback action buttons are site-aware:
- Failover is shown only on the secondary site and only when Protection Group status is
active. The panel checks whether the local site ID matchessecondary_site_idon the Protection Group object. - Failback is shown only on the original primary site and only when status is
failed_over. The panel usesprimary_site_id(the original primary, not the post-failover current primary) for this check. - The panel uses the
statusfield as the authoritative signal rather thancurrent_primary_site_id, because cross-site sync ofcurrent_primary_site_idmay be incomplete due to foreign-key constraints on the remote site's database. - If the local site cannot be determined (e.g. the
GET /v1/{tenant_id}/sites/localcall fails), both buttons default to visible and the server enforces the site check, returning HTTP 400 (InvalidExecutionSite) if the operation is submitted from the wrong site.
Member eligibility pre-checks
Before submitting an Add VM request, you can use the validate endpoint to detect problems without side effects:
POST /v1/{tenant_id}/protection-groups/{pg_id}/members/validate
This returns HTTP 200 with a structured errors array. The dashboard does not currently call this endpoint automatically, but you can call it directly or via the CLI (openstack dr protection group member validate) before using the Add VM dialog.
Recovery points and ActiveDR
The Create Recovery Point batch action in the index table (icon: camera) is only enabled for Protection Groups using replication_type: active_dr. For async Protection Groups, recovery points are created automatically on the replication interval; on-demand creation is an ActiveDR-specific feature. The underlying endpoint is:
POST /v1/{tenant_id}/protection-groups/{id}/recovery-points
Metadata sync
The Sync batch action calls:
POST /v1/{tenant_id}/protection-groups/{id}/sync-metadata
This detects VM changes — new disks, new network attachments — and replicates updated metadata to the peer site. Run sync after adding a new volume to a protected VM so the engine is aware of the new attachment before the next failover.
Resource mapping widget behaviour
The network and flavor mapping widget in the Failover and Test Failover dialogs auto-selects a target resource whose name exactly matches the source name. Where no name match exists on the target site, the widget presents a — match by name — option (empty value), which instructs the engine to resolve by name at execution time. Selecting a specific target UUID overrides name-based resolution. If the target site has no resources of that type, the dropdown shows No resources on target site and you must create the corresponding resource on the secondary site before failover.
Dashboard package name
The Horizon plugin is shipped as the Debian package python3-triliositerecovery-dashboards. The Python module installed by this package is protector_dashboards (the module retains the original product name). The Juju dashboard charm is named trilio-site-recovery-for-openstack-dashboard on Charmhub.
Direct editing of protector.conf is not supported
In Juju charm deployments, protector.conf is rendered by the charm from relation data and juju config values. Do not edit /etc/protector/protector.conf directly — the charm overwrites manual edits on every config-changed event. Use juju config trilio-site-recovery <key>=<value> with hyphenated option names instead.
openstackclient snap incompatibility
The openstack dr CLI commands shown in the examples require the python3-triliositerecovery package installed alongside python-openstackclient installed via pip, not via snap. The openstackclient snap cannot load third-party CLI plugins and will not expose openstack dr subcommands.