OpenStack Integration
How it uses Keystone, the message bus, and other services
This page explains how Trilio Share Protection for OpenStack (Abacá) integrates with Keystone, the message bus, and other OpenStack services. Understanding these integration points matters because Abacá is a first-class OpenStack service: it registers in the Keystone service catalog, uses oslo.messaging for API-to-conductor communication, delegates secret management to Barbican, and coordinates with Nova, Neutron, Manila, and Glance to run backup and restore jobs. Operators deploying Abacá or developers building against its API need to know how authentication flows from a tenant request through to a worker VM, how secrets are isolated from the control plane, and why certain architectural choices — such as a dedicated RabbitMQ broker and outbound-only worker transport — are non-negotiable for security and deployability.
Before reading this page, you should have:
- RHOSO ≥ 18 with OpenShift ≥ 4.14 hosting the control plane
- OpenStack Keystone ≥ 2023.1 with domain-scoped tokens and trusts enabled
- OpenStack Manila (Shared File System service)
- OpenStack Barbican (Key Manager service)
- OpenStack Nova and Neutron for worker VM lifecycle
- OpenStack Glance for worker image storage
- A dedicated MySQL or MariaDB (Galera) instance for Abacá's bookkeeping database
- A dedicated RabbitMQ instance for Abacá's oslo.messaging bus — never the platform's shared broker
- S3-compatible object storage with versioning and Object Lock enabled at bucket creation
- Familiarity with Keystone concepts: service catalog, roles, trusts, domain-scoped tokens
- The Abacá deployment scripts from
deploy/rhoso/checked out and yourclouds.yamlconfigured
Abacá's OpenStack integration is established by three deployment scripts that must run in order. Each script is idempotent — re-running it is safe and will not rotate passwords unless you set ABACA_ROTATE=1.
Step 1 — Register the Keystone identity
Run deploy/rhoso/01-identity.sh. This script:
- Creates the
abacaOpenShift namespace and Abacá's own certificate authority - Generates the service account password and stores it in an OpenShift Secret (
abaca-secret) - Applies a
KeystoneServiceCR in the control-plane namespace; the keystone-operator creates both theabacaKeystone service user and the catalog service record automatically - Creates the
abacaworker project, which is the OpenStack project where worker VMs run - Grants the service user the
serviceandadminroles on the service project, andadminon the worker project - Creates the
abaca_domain_ownerrole and grants it to the bootstrap admin user so that a Domain owner can immediately create backup target templates - Records the bootstrap domain-to-service-project mapping in a ConfigMap (
abaca-domain-map) for the database migration step
cd deploy/rhoso
bash 01-identity.sh
Wait for the script to report OK. and confirm the KeystoneService reached Ready status:
oc -n openstack get keystoneservice abaca -o jsonpath='{.status.conditions}'
Step 2 — Register catalog endpoints
Run deploy/rhoso/02-catalog.sh. This applies a KeystoneEndpoint CR that registers the public and internal interfaces for the share-protection service type. There is no admin interface — modern RHOSO deployments register only public and internal.
bash 02-catalog.sh
Verify the endpoints are visible in the catalog:
openstack endpoint list --service abaca -c Interface -c URL -c Enabled -f table
You should see two rows: one public URL (an OpenShift Route) and one internal URL (an in-cluster Service).
Step 3 — Provision the message bus
Run deploy/rhoso/04-messaging.sh. This creates Abacá's own dedicated RabbitMQ instance in the abaca namespace and provisions a TransportURL CR to generate the connection string. The script deliberately avoids the platform's shared broker — see Configuration for why this is a hard requirement.
bash 04-messaging.sh
The script waits up to 15 minutes for the RabbitMq CR and the TransportURL CR to reach Ready. When it prints OK., the transport URL secret is available in the abaca namespace for the deployment step to consume.
Step 4 — Grant the domain-owner role to real users
After the initial bootstrap, grant the abaca_domain_owner role to the users who will register S3 buckets for each Keystone Domain:
openstack role add \
--user <username> \
--user-domain <domain-name> \
--domain <domain-name> \
abaca_domain_owner
Without this role, no user in that Domain can create a BackupTargetTemplate or register an S3 bucket — every such request returns HTTP 403.
Abacá is configured with an oslo.config INI file. The options below govern OpenStack integration specifically; see the deployment guide for the full annotated sample configuration.
[DEFAULT] — Service catalog discovery
Abacá discovers other OpenStack services via the Keystone catalog. These options control which service type name it looks for and which endpoint interface it calls.
| Option | Type | Default | Purpose |
|---|---|---|---|
catalog_type | string | share-protection | The Keystone service type under which Abacá's own endpoints are registered. Change this only if you registered a non-standard type. |
endpoint_type | string | publicURL | The catalog interface Abacá uses when calling other OpenStack services. Valid values: publicURL, adminURL, internalURL. Use internalURL in production to keep inter-service traffic on the internal network. |
[abaca] — Service identity and worker project
These options identify Abacá's own service account and the OpenStack project where worker VMs run. The conductor uses these to scope Nova, Neutron, and Glance API calls when booting and managing the worker fleet.
| Option | Type | Default | Purpose |
|---|---|---|---|
service_user_id | string | — | Keystone UUID of the Abacá service user. Set this or service_user_name; the conductor uses it to scope its own token. |
service_user_name | string | — | Name of the Abacá service user, as an alternative to service_user_id. |
worker_project_id | string | — | Keystone UUID of the project where worker VMs are booted. Used as a single-Domain fallback when no domain_service_projects row exists. |
worker_project_name | string | — | Name of the worker project, as an alternative to worker_project_id. |
[api] — API service binding and behaviour
| Option | Type | Default | Purpose |
|---|---|---|---|
bind_host | string | 0.0.0.0 | Address the gunicorn workers bind to inside the pod. |
bind_port | integer | 9797 | Port the API listens on. |
noauth | boolean | false | Disables Keystone authentication entirely. Never set true in production — this is a dev-only mode for the docker-compose stack. |
max_limit | integer | 1000 | Hard cap on items returned per list request. |
default_limit | integer | 100 | Default page size when the caller does not supply a limit parameter. |
[database] — Database connection
| Option | Type | Default | Purpose |
|---|---|---|---|
connection | string | — | SQLAlchemy connection URL for Abacá's dedicated MySQL/MariaDB (Galera) database, e.g. mysql+pymysql://abaca:PASSWORD@HOST/abaca. Abacá stores only bookkeeping metadata here — no secret values, no backup data. |
[conductor] — Orchestration and worker fleet
These options govern how the conductor interacts with Nova and Neutron to manage the worker VM fleet, and how frequently it runs its reconciliation sweeps.
| Option | Type | Default | Purpose |
|---|---|---|---|
scheduler_interval | integer | 60 | Seconds between policy scheduling sweeps. Lower values reduce backup start latency but increase database load. |
reconciliation_interval | integer | 300 | Seconds between orphan-sweep and stuck-job recovery passes. |
worker_heartbeat_deadline_seconds | integer | 60 | A worker that has not sent a heartbeat within this window is considered dead and its jobs are recovered. |
worker_heartbeat_interval_seconds | integer | 10 | How often a healthy worker sends a heartbeat to the conductor. |
sweep_command_wait_seconds | integer | 120 | Seconds the reconciliation loop waits for a command to be claimed before marking it stale. |
worker_token_ttl_seconds | integer | 1800 | Lifetime of the short-lived Keystone token issued to a worker VM at boot. |
maintenance_interval_seconds | integer | 604800 | Seconds between Kopia repository maintenance runs (default: one week). |
catalogue_sync_interval_seconds | integer | 3600 | How often the conductor reconciles its internal share catalogue against the Manila API. |
usage_sample_interval_seconds | integer | 21600 | Frequency of usage/metering samples for chargeback (default: six hours). |
min_workers | integer | 1 | Minimum number of healthy worker VMs the conductor maintains in the fleet. |
worker_boot_cooldown_seconds | integer | 180 | Minimum time between successive worker boot attempts. |
worker_boot_max_failures | integer | 3 | If this many boot attempts fail within worker_boot_failure_window_seconds, the conductor stops trying and raises operator_action_required. |
worker_boot_failure_window_seconds | integer | 7200 | Rolling window for counting boot failures. |
worker_boot_grace_seconds | integer | 1200 | Time allowed for a newly booted worker to register before it is considered failed. |
worker_boot_os_cloud | string | abaca-service | The clouds.yaml cloud name the conductor uses to call Nova/Neutron when booting worker VMs. |
worker_boot_image | string | abaca-worker-0.23.1 | Glance image name (must carry the abaca_worker_image=1 property) used when booting new workers. |
worker_boot_flavor | string | m1.small | Nova flavor for worker VMs. |
worker_boot_network | string | — | Neutron network ID or name to attach worker VMs to at boot. |
worker_boot_key_name | string | — | Nova key pair name injected into worker VMs for emergency SSH access. |
worker_api_url | string | — | Base URL the conductor passes to new worker VMs so they know where to claim leased commands. |
worker_api_ca_file | string | — | Path to the CA bundle the conductor presents to workers for TLS verification of the API endpoint. |
worker_api_host_aliases | list | "" | Hostname aliases injected into worker cloud-init when the API endpoint resolves to an IP the worker network cannot route. |
queued_job_deadline_seconds | integer | 300 | A job that remains queued longer than this is considered stuck and recovered by the reconciliation loop. |
[kopia] — Backup engine
| Option | Type | Default | Purpose |
|---|---|---|---|
binary | string | kopia | Path to the Kopia binary inside the worker VM. Should not need changing if the image is built with deploy/rhoso/worker/20-worker-image.sh. |
require_fips_profile | boolean | true | Reject repository initialization if the FIPS cryptographic profile (AES-256-GCM-HMAC-SHA256, PBKDF2) cannot be applied. Set false only in development environments that lack a FIPS-built Kopia binary. |
subprocess_timeout | integer | 3600 | Maximum seconds a single Kopia subprocess may run before the worker agent kills it. |
executor | string | — | How Kopia is invoked. http is the production path (worker VM fleet). ephemeral_container is docker-compose dev-only and is not paired with the worker fleet. Valid values: http, ephemeral_container. |
[worker] — Worker agent configuration
These options are set in the configuration of the abaca-worker-agent process running inside each worker VM, not in the control-plane configuration.
| Option | Type | Default | Purpose |
|---|---|---|---|
capacity_slots | integer | — | Maximum number of concurrent Kopia jobs this worker will accept. The conductor's fleet picker only assigns a job to a worker whose running job count is strictly below this limit. |
mount_base | string | /var/lib/abaca/mnt | Directory under which the agent creates per-job NFS mount points. |
command_lease_seconds | integer | 120 | How long a claimed command lease is valid. The agent renews it via progress updates before expiry. |
max_command_duration_seconds | integer | 21600 | Hard limit (six hours) on any single command. The agent abandons a command and reports failure if this is exceeded. |
long_poll_seconds | integer | 20 | How long the agent holds the GET /v1/workers/{id}/commands?wait=… request open when there are no pending commands. |
progress_interval_seconds | integer | 30 | How often the agent posts progress updates (which also renew the command lease). |
api_url | string | — | Base URL of the Abacá API, passed to the worker at boot via cloud-init. |
token | string | — | Short-lived Keystone token the conductor issues at boot (worker_token_ttl_seconds). |
api_ca_file | string | — | Path to the CA bundle the agent uses to verify the API endpoint's TLS certificate. |
[enrollment] — Target enrollment dispatch
| Option | Type | Default | Purpose |
|---|---|---|---|
dispatch | string | — | Controls how S3 bucket enrollment jobs are dispatched. rpc sends them through the conductor to the worker fleet (production). inline runs enrollment in the conductor process — for testing only. Valid values: rpc, inline. |
How authentication works
Every request to the Abacá API must carry a Keystone token in the X-Auth-Token header, exactly as with any other OpenStack service. The API's keystonemiddleware validates the token against Keystone and populates the request context with the caller's project, domain, and roles. Access control is then enforced by oslo.policy rules.
Abacá registers in the Keystone service catalog under the share-protection service type. Clients discover the API endpoint the same way they discover any other OpenStack service — from the catalog or via openstack catalog show abaca.
How secrets are kept off the bus
Abacá never stores S3 credentials or Kopia repository passwords in its own database or on the message bus. When a Domain owner registers an S3 bucket, Abacá stores the credentials in Barbican and records only the Barbican secret href in its database. When the conductor dispatches a job, the message carries that href and a Keystone trust ID — never the secret value itself. The worker VM fetches the secret from Barbican at job time, holds it only in memory for the duration of the job, and discards it afterward.
How workers authenticate
Worker VMs do not use the shared message bus. Instead, the conductor issues each worker a short-lived Keystone token (valid for worker_token_ttl_seconds, default 30 minutes) at boot time, delivered via cloud-init. The worker uses this token to call GET /v1/workers/{id}/commands on the Abacá API, which holds the request open (long-poll) until a command is available or the wait timeout (long_poll_seconds) expires. This outbound-only model means workers require no inbound network access and introduce no shared credential whose blast radius exceeds their own jobs.
How Keystone trusts are used
Abacá uses Keystone trusts to act on behalf of a tenant without holding the tenant's password. When a tenant creates a policy or registers a target, Abacá requests a trust delegation scoped to the specific operations it needs — reading Barbican secrets and managing Manila access rules. The tenant can revoke the trust at any time, which immediately prevents Abacá from acting on that tenant's behalf. Trust IDs are carried on the message bus; the actual trust token is materialized at job time.
Interacting with the API
Abacá's API is versioned at /v1. You can interact with it three ways:
OpenStack CLI (the python-abacaclient plugin provides openstack share protection … subcommands):
# List protection policies for the current project
openstack share protection policy list
# Request an on-demand backup
openstack share protection backup create --share <share-id> --policy <policy-id>
# Check job status
openstack share protection job show <job-id>
REST API directly:
# Obtain a token
OS_TOKEN=$(openstack token issue -f value -c id)
ABACA_URL=$(openstack catalog show abaca -f value -c endpoints \
| grep public | awk '{print $2}')
# List targets visible to the current domain
curl -s -H "X-Auth-Token: ${OS_TOKEN}" \
"${ABACA_URL}/v1/targets" | python3 -m json.tool
Python SDK (the python-abacaclient library):
from abacaclient import Client
import openstack
conn = openstack.connect(cloud='mycloud')
client = Client(session=conn.session)
for policy in client.policies.list():
print(policy.id, policy.share_id, policy.schedule)
Registering a Domain and its service project
After the initial install, each Keystone Domain that needs backup coverage must be registered with abaca-manage:
# Register a domain and map it to its dedicated service project
abaca-manage domain_register \
--domain-id <keystone-domain-id> \
--service-project-id <openstack-project-id>
Without this mapping, the API cannot determine which project should own worker VMs and S3 credentials for that Domain, and users in that Domain cannot register backup targets.
Using the Horizon plugin
If abaca-dashboard is installed (see deploy/rhoso/08-dashboard.sh), tenant users see a Share Protection panel in the Horizon project view covering targets, policies, backups, restores, and usage. Operators see an additional Admin panel showing fleet health and coverage status across all projects.
Example 1 — Verify catalog registration
After running 02-catalog.sh, confirm that both interfaces are registered and point to the correct URLs.
openstack endpoint list --service abaca -c Interface -c URL -c Enabled -f table
Expected output:
+-----------+----------------------------------------------+---------+
| Interface | URL | Enabled |
+-----------+----------------------------------------------+---------+
| public | https://abaca.apps.cluster.example.com/v1 | True |
| internal | http://abaca-api.abaca.svc.cluster.local/v1 | True |
+-----------+----------------------------------------------+---------+
Example 2 — Confirm the service user has the correct role grants
openstack role assignment list \
--user abaca \
--names \
-c Role -c Project -c Domain -f table
Expected output (exact project and domain names depend on your deployment):
+-------+------------------+--------+
| Role | Project | Domain |
+-------+------------------+--------+
| admin | abaca | |
| admin | service | |
| service | service | |
+-------+------------------+--------+
Example 3 — Grant the domain-owner role to a real user
After bootstrap, grant the role to the user who will manage backup targets for a Domain:
openstack role add \
--user alice \
--user-domain acme \
--domain acme \
abaca_domain_owner
Verify the grant:
openstack role assignment list \
--user alice \
--domain acme \
--names \
-c Role -c Domain -f table
Expected output:
+--------------------+--------+
| Role | Domain |
+--------------------+--------+
| abaca_domain_owner | acme |
+--------------------+--------+
Example 4 — Register a Domain with its service project using abaca-manage
Resolve the IDs first, then register:
DOMAIN_ID=$(openstack domain show acme -f value -c id)
PROJECT_ID=$(openstack project show abaca-acme -f value -c id)
abaca-manage domain_register \
--domain-id "${DOMAIN_ID}" \
--service-project-id "${PROJECT_ID}"
Example 5 — Annotated conductor configuration for OpenStack integration
The following snippet shows the options most relevant to OpenStack integration. Place this in /etc/abaca/abaca.conf (or the path mounted into the abaca-conductor pod):
[DEFAULT]
# The catalog service type Abacá registers under
catalog_type = share-protection
# Use the internal endpoint for inter-service calls
endpoint_type = internalURL
[abaca]
# The Keystone user Abacá uses for all service-level API calls
service_user_name = abaca
# The project where worker VMs are booted (single-Domain fallback)
worker_project_name = abaca
[api]
bind_host = 0.0.0.0
bind_port = 9797
# Never set noauth = true in production
noauth = false
default_limit = 100
max_limit = 1000
[conductor]
# Minimum workers the fleet must maintain
min_workers = 2
# Glance image for new workers (must have abaca_worker_image=1 property)
worker_boot_image = abaca-worker-0.23.1
worker_boot_flavor = m1.medium
# clouds.yaml entry the conductor uses to call Nova/Neutron
worker_boot_os_cloud = abaca-service
# URL workers use to claim leased commands
worker_api_url = https://abaca.apps.cluster.example.com/v1
# CA bundle for workers to verify the API endpoint
worker_api_ca_file = /etc/abaca/ca-bundle.pem
# Seconds a job may remain queued before the reconciliation loop recovers it
queued_job_deadline_seconds = 300
[database]
connection = mysql+pymysql://abaca:REDACTED@mariadb.abaca.svc.cluster.local/abaca
[kopia]
binary = kopia
require_fips_profile = true
executor = http
Example 6 — Trigger an on-demand backup via the CLI
openstack share protection backup create \
--share a1b2c3d4-0000-0000-0000-111122223333 \
--policy e5f6a7b8-0000-0000-0000-444455556666
Then poll the resulting job until it reaches available:
watch -n 5 openstack share protection job show <job-id> -f table
Issue: KeystoneService CR never reaches Ready
Symptom: 01-identity.sh times out waiting for the KeystoneService to become Ready; oc -n openstack get keystoneservice abaca -o jsonpath='{.status.conditions}' shows a non-True status.
Likely cause: The abaca-secret Secret does not exist in the control-plane namespace (openstack), or the AbacaPassword key is missing. The keystone-operator requires the secret to be present in its own namespace.
Fix: Confirm the secret exists in both namespaces:
oc -n abaca get secret abaca-secret -o jsonpath='{.data.AbacaPassword}' | base64 -d
oc -n openstack get secret abaca-secret -o jsonpath='{.data.AbacaPassword}' | base64 -d
If the openstack-namespace copy is missing, the script copies it automatically — re-run 01-identity.sh.
Issue: 403 Forbidden when creating a BackupTargetTemplate
Symptom: A Domain user receives HTTP 403 when attempting to create a backup target template, even when logged in with an admin-equivalent account.
Likely cause: The abaca_domain_owner role has not been granted on the Domain (not the project). The role must be a domain-scoped assignment; a project-scoped admin role does not satisfy the policy rule, and a domain-scoped token cannot be issued without a domain role assignment.
Fix:
openstack role add \
--user <username> \
--user-domain <domain-name> \
--domain <domain-name> \
abaca_domain_owner
Issue: 401 Unauthorized on every API request
Symptom: All API calls return 401 even with a valid token.
Likely cause 1: The catalog_type in abaca.conf does not match the service type registered in step 2 (share-protection). The keystonemiddleware cannot find the service in the catalog to validate against.
Likely cause 2: noauth = true has been set in the [api] section of a production deployment (makes no difference to 401s but disables all auth; check that it is false).
Fix: Confirm the registered service type:
openstack service show abaca -c type -f value
Ensure [DEFAULT] catalog_type in abaca.conf matches exactly. Restart the abaca-api pods after any config change:
oc -n abaca rollout restart deployment abaca-api
Issue: Workers register but jobs remain queued indefinitely
Symptom: Worker VMs appear healthy (heartbeats arriving), but backup jobs stay in the queued state past queued_job_deadline_seconds.
Likely cause: The conductor cannot issue leased commands because worker_api_url is not set or resolves to an address the worker cannot reach. Workers cannot claim commands without a reachable API URL.
Fix: Confirm the value in [conductor] worker_api_url points to the public Abacá API endpoint and that the route is accessible from the worker VM's network. Check the conductor logs:
oc -n abaca logs deployment/abaca-conductor --tail=100 | grep -i worker
Issue: TransportURL CR never reaches Ready
Symptom: 04-messaging.sh times out; oc -n abaca get transporturl abaca-transport -o jsonpath='{.status}' shows no secretName.
Likely cause: The RabbitMQ CR is not yet Ready, or the cert-manager certificate for the broker has not been issued. The rabbitmq-operator will not provision the vhost or user until the broker is healthy.
Fix: Check the broker first:
oc -n abaca get rabbitmqs.rabbitmq.openstack.org -o wide
oc -n abaca describe rabbitmqs.rabbitmq.openstack.org abaca-rabbitmq
If the broker is Running but not Ready, inspect the certificate:
oc -n abaca describe certificate cert-abaca-rabbitmq
Issue: Domain users see no "Register Bucket" button in Horizon
Symptom: The Share Protection panel is visible but no option to register a bucket appears, even in the service project.
Likely cause: The domain_service_projects database row for this Domain is missing. Without it, GET /v1/domain reports is_service_project: false for every project, and the dashboard hides the registration UI.
Fix: Register the Domain-to-service-project mapping:
DOMAIN_ID=$(openstack domain show <domain-name> -f value -c id)
PROJECT_ID=$(openstack project show <service-project-name> -f value -c id)
abaca-manage domain_register \
--domain-id "${DOMAIN_ID}" \
--service-project-id "${PROJECT_ID}"
Issue: Worker VM boots but does not register with the conductor
Symptom: A worker VM reaches ACTIVE in Nova but never appears in the Abacá worker fleet; openstack share protection commands report insufficient fleet capacity.
Likely cause 1: The Glance image used to boot the worker does not have the abaca_worker_image=1 property. An unprepared image will not start the abaca-worker-agent systemd unit.
Likely cause 2: worker_api_url was not injected into the worker via cloud-init, so the agent has no endpoint to call.
Fix: Verify the Glance image property:
openstack image show <image-name> -c properties -f json | python3 -m json.tool
Confirm abaca_worker_image is "1". If the property is missing, rebuild the image using deploy/rhoso/worker/20-worker-image.sh and re-upload to Glance. Check the [conductor] worker_boot_image option matches the correct image name.