Python API
Using the service's Python client / SDK bindings
This page explains how to use the python-abacaclient Python SDK to interact programmatically with the Trilio Share Protection for OpenStack (Abacá) REST API. The SDK is a thin HTTP client that speaks directly to the Abacá /v1 REST API over HTTPS and has no dependency on the Abacá server package — you can use it from scripts, automation pipelines, or Horizon views without installing any server-side components. This page covers installation, authentication, the Client class and its methods, the enrollment orchestration helper, and common usage patterns with realistic examples.
Before using the Python SDK, ensure you have:
- Python ≥ 3.11 installed on your workstation or automation environment.
- Abacá deployed on RHOSO 18 or later and registered in the Keystone service catalog under service type
share-protection. - A valid Keystone token or a configured
keystoneauth1Session with appropriate scope for the operations you intend to perform:- Project-scoped token/session: required for backup, restore, policy, and job operations.
- Domain-scoped token/session: required for
register-bucket(bucket enrollment) API calls.
- Barbican available and reachable, if you are using the enrollment orchestration helper to store S3 credentials.
keystoneclientandbarbicanclientPython packages, if you are using the enrollment helper (abacaclient.enrollment).- Network access from your client to the Abacá API endpoint (discoverable from the Keystone service catalog).
The python-abacaclient package is distributed as part of the Abacá release. Installation method depends on your environment:
On a developer workstation (local dev stack)
The local dev stack is managed with Docker Compose. The client library is available inside the dev environment automatically. To install it into your local Python environment for scripting or testing:
pip install python-abacaclient
On a RHOSO 18 deployment (Horizon plugin path)
The python-abacaclient package is layered into the Horizon pod venv by the dashboard install script. You do not install it manually on a production RHOSO deployment — the script deploy/rhoso/08-dashboard.sh handles this as part of deploying the abaca-dashboard plugin.
Verifying the install
After installation, confirm the package is importable:
from abacaclient.client import Client
print("python-abacaclient is ready")
For integration testing against a live cluster, the abaca-tempest-plugin package provides the Tempest test classes. That package is installed separately as part of your Tempest environment setup — refer to the Tempest integration guide for instructions.
The Client class accepts all configuration at construction time. There are no environment variable defaults or config files — you supply the endpoint, credentials, and TLS settings explicitly.
Client constructor parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
endpoint | str | Yes | Base URL of the Abacá service, without the /v1 prefix. The client appends /v1 automatically. Discover this from the Keystone catalog under service type share-protection. |
token | str | No | A Keystone token sent as X-Auth-Token. Used when session is not provided. |
session | keystoneauth1.session.Session | No | A bound keystoneauth1 Session. When provided, all requests are issued through it instead of the requests library directly. Recommended for production use. |
ca_cert | str | No | Path to a CA bundle file for TLS verification. Passed to requests as the verify parameter. If omitted, standard system CA verification applies. |
Choosing between token and session
tokenmode is convenient for one-off scripts where you already have a token (for example, fromopenstack token issue).sessionmode is recommended for long-running automation or Horizon views. The keystoneauth1 Session handles token refresh automatically and integrates withclouds.yamlcredential management.
Scope requirements for specific operations
Some API calls require a domain-scoped token or session rather than a project-scoped one:
bucket_register()— theregister-bucketendpoint is a Domain-owner action and will return HTTP 403 with a project-scoped token.template_create(),template_list(),template_show(),template_set(),template_delete()— Domain-owner actions.
All other operations (policies, backups, restores, jobs, usage) use a standard project-scoped token.
Endpoint discovery
Rather than hard-coding the endpoint, use abacaclient.enrollment.find_abaca_endpoint() to discover it from the Keystone catalog:
from abacaclient.enrollment import find_abaca_endpoint
endpoint = find_abaca_endpoint(session)
This resolves the share-protection service type, preferring the public interface and falling back to internal.
Constructing a client
With a raw Keystone token:
from abacaclient.client import Client
client = Client(
endpoint="https://abaca.example.com",
token="<your-keystone-token>",
)
With a keystoneauth1 Session (recommended):
import openstack
from abacaclient.client import Client
from abacaclient.enrollment import find_abaca_endpoint
conn = openstack.connect(cloud="mycloud")
session = conn.session
endpoint = find_abaca_endpoint(session)
client = Client(endpoint=endpoint, session=session)
Error handling
All non-2xx responses raise abacaclient.exceptions.AbacaClientError. This exception carries:
.status— the HTTP status code (integer)- The error envelope fields from the response body:
code,title,detail,category
The category field will be either tenant_action_required or operator_action_required, indicating who must act to resolve the failure.
from abacaclient.exceptions import AbacaClientError
try:
backup = client.backup_show("nonexistent-id")
except AbacaClientError as exc:
print(f"HTTP {exc.status}: {exc}")
Managing target templates
Target templates are Domain-level objects. Use a domain-scoped session for these calls.
# Create a template
template = client.template_create(
name="primary-s3",
s3_endpoint="https://s3.us-east-1.wasabisys.com",
s3_region="us-east-1",
bucket_scheme="per_project",
)
# List templates (with pagination)
templates = client.template_list(limit=50)
# Show a single template
template = client.template_show(template["id"])
# Update a template (partial update; bucket_scheme cannot be changed)
client.template_set(template["id"], name="primary-s3-updated")
# Delete a template
client.template_delete(template["id"])
Registering and managing buckets
Bucket registration passes Barbican hrefs — never raw S3 credentials. Use the enrollment helper (see below) to store credentials in Barbican and obtain the hrefs.
# Register a bucket under a template
result = client.bucket_register(
template["id"],
bucket_name="myproject-backups",
project_id="<project-uuid>",
s3_access_key_ref="<barbican-href-access-key>",
s3_secret_key_ref="<barbican-href-secret-key>",
kopia_key_trust_id="<trust-id>",
)
# List buckets under a template
buckets = client.bucket_list(template["id"])
# Update credential references (repair path only)
client.bucket_update(
template["id"],
bucket["id"],
s3_access_key_ref="<new-barbican-href>",
)
# Deregister a bucket
client.bucket_deregister(template["id"], bucket["id"])
Managing policies
A policy binds a Manila share to a target template, defines the backup schedule as a cron expression, and sets retention rules.
# Create a policy
policy = client.policy_create(
share_id="<manila-share-uuid>",
target_template_id=template["id"],
schedule="0 2 * * *",
retention_days=30,
)
# List policies
policies = client.policy_list(limit=100)
# Show a policy
policy = client.policy_show(policy["id"])
# Update schedule or retention (share_id and target_template_id cannot be changed)
client.policy_update(policy["id"], schedule="0 3 * * *", retention_days=14)
# Delete a policy
client.policy_delete(policy["id"])
Creating and managing backups
# Request an on-demand backup
backup = client.backup_create(
share_id="<manila-share-uuid>",
target_template_id=template["id"],
)
# List backups
backups = client.backup_list(limit=50, marker="<last-seen-id>")
# Show a backup
backup = client.backup_show(backup["id"])
# Delete a backup
client.backup_delete(backup["id"])
Requesting restores
# Restore to an existing share
restore = client.restore_create(
backup_id="<backup-uuid>",
target_share_id="<manila-share-uuid>",
)
# List restores
restores = client.restore_list()
# Show a restore
restore = client.restore_show(restore["id"])
# Delete a restore record
client.restore_delete(restore["id"])
Monitoring jobs
Every backup, restore, and enrollment runs as a Job with a defined lifecycle: queued → provisioning_network → provisioning_source → connecting_repository → transferring → finalizing → releasing → available (or error).
# List recent jobs
jobs = client.job_list(limit=20)
# Poll a specific job until terminal
import time
def wait_for_job(client, job_id, timeout=600, interval=10):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
job = client.job_show(job_id)
state = job.get("state")
if state in ("available", "error"):
return job
time.sleep(interval)
raise TimeoutError(f"Job {job_id} did not complete within {timeout}s")
job = wait_for_job(client, backup["job_id"])
if job["state"] == "error":
print(f"Backup failed: {job.get('error_detail')}")
print(f"Action required by: {job.get('error_category')}")
Usage and admin endpoints
# Show usage/metering data for the current project
usage = client.usage_show()
# Admin: list worker VMs in the fleet
workers = client.worker_list()
# Admin: list backup coverage across shares
coverage = client.coverage_list()
# Domain info (service project, custody, scope)
domain = client.domain_show()
Using the enrollment orchestration helper
The abacaclient.enrollment module composes the full target enrollment workflow: storing S3 credentials in Barbican, creating a Keystone trust for the Abacá service user, and calling register-bucket on the API. Use it when you need to enroll a new backup target programmatically.
import openstack
from abacaclient.enrollment import EnrollmentRequest, enroll, wait_for_terminal
from abacaclient.enrollment import find_abaca_endpoint
conn = openstack.connect(cloud="mycloud")
session = conn.session
abaca_url = find_abaca_endpoint(session)
request = EnrollmentRequest(
template_id="<template-uuid>",
bucket="myproject-backups",
project_id=conn.current_project_id,
access_key="<s3-access-key>", # never logged or repr'd
secret_key="<s3-secret-key>", # never logged or repr'd
trust_roles=("member", "creator"),
)
result = enroll(
session,
request,
trustor_user_id=conn.current_user_id,
project_id=conn.current_project_id,
)
print(f"Bucket registered: {result.target.get('id')}")
print(f"Enrollment job: {result.job.get('id')}")
# Wait for the enrollment job to complete
if result.job:
final = wait_for_terminal(
session,
abaca_url,
result.job["id"],
timeout=300,
interval=10,
)
print(f"Enrollment state: {final['state']}")
Important: register-bucket requires a domain-scoped token. If your session is project-scoped (as is common in Horizon), pass a separately constructed domain-scoped session as api_session:
result = enroll(
session, # project-scoped: used for Barbican + trust creation
request,
trustor_user_id=conn.current_user_id,
project_id=conn.current_project_id,
api_session=domain_session, # domain-scoped: used for register-bucket
)
Example 1 — Discover the endpoint and list all backups
import openstack
from abacaclient.client import Client
from abacaclient.enrollment import find_abaca_endpoint
conn = openstack.connect(cloud="mycloud")
endpoint = find_abaca_endpoint(conn.session)
client = Client(endpoint=endpoint, session=conn.session)
backups = client.backup_list(limit=10)
for b in backups.get("backups", []):
print(b["id"], b["state"], b.get("created_at"))
Expected output:
3a1b2c3d-... available 2024-11-01T02:01:17Z
7e8f9a0b-... available 2024-10-31T02:00:55Z
Example 2 — Create a policy and trigger an on-demand backup
from abacaclient.client import Client
from abacaclient.enrollment import find_abaca_endpoint
import openstack
import time
conn = openstack.connect(cloud="mycloud")
endpoint = find_abaca_endpoint(conn.session)
client = Client(endpoint=endpoint, session=conn.session)
# Bind a share to a target template with a daily schedule
policy = client.policy_create(
share_id="<manila-share-uuid>",
target_template_id="<template-uuid>",
schedule="0 2 * * *",
retention_days=30,
)
print(f"Policy created: {policy['id']}")
# Request an on-demand backup immediately
backup = client.backup_create(
share_id="<manila-share-uuid>",
target_template_id="<template-uuid>",
)
job_id = backup.get("job_id")
print(f"Backup requested, job: {job_id}")
# Poll until terminal
while True:
job = client.job_show(job_id)
state = job["state"]
print(f" state={state} progress={job.get('progress')}")
if state in ("available", "error"):
break
time.sleep(15)
if state == "available":
print(f"Backup complete: {backup['id']}")
else:
print(f"Backup failed: {job.get('error_detail')} (category={job.get('error_category')})")
Expected output:
Policy created: a1b2c3d4-...
Backup requested, job: f5e6d7c8-...
state=provisioning_network progress=None
state=transferring progress=42
state=available progress=100
Backup complete: 9a8b7c6d-...
Example 3 — Restore a backup to an existing share
restore = client.restore_create(
backup_id="9a8b7c6d-...",
target_share_id="<destination-manila-share-uuid>",
)
print(f"Restore job: {restore.get('job_id')}")
Expected output:
Restore job: c3d4e5f6-...
Example 4 — Paginate through all jobs using a marker
all_jobs = []
marker = None
while True:
page = client.job_list(limit=50, marker=marker)
jobs = page.get("jobs", [])
if not jobs:
break
all_jobs.extend(jobs)
marker = jobs[-1]["id"]
print(f"Total jobs found: {len(all_jobs)}")
errors = [j for j in all_jobs if j["state"] == "error"]
print(f"Jobs in error state: {len(errors)}")
for j in errors:
print(f" {j['id']} category={j.get('error_category')} detail={j.get('error_detail')}")
Example 5 — Full enrollment with adoption of an existing Kopia repository
If you have an S3 bucket that already contains a Kopia repository (for example, migrating from another installation), pass repository_password to adopt it rather than initializing a new one.
from abacaclient.enrollment import EnrollmentRequest, enroll, wait_for_terminal, find_abaca_endpoint
import openstack
conn = openstack.connect(cloud="mycloud")
session = conn.session
abaca_url = find_abaca_endpoint(session)
request = EnrollmentRequest(
template_id="<template-uuid>",
bucket="existing-repo-bucket",
project_id=conn.current_project_id,
access_key="<s3-access-key>",
secret_key="<s3-secret-key>",
repository_password="<existing-kopia-repo-password>", # adoption path
)
result = enroll(
session,
request,
trustor_user_id=conn.current_user_id,
project_id=conn.current_project_id,
)
final = wait_for_terminal(
session, abaca_url, result.job["id"], timeout=300, interval=10
)
print(f"Adoption {final['state']}: bucket={result.target.get('id')}")
Example 6 — Admin: check fleet health and coverage
# These endpoints require an admin-scoped token
workers = client.worker_list()
for w in workers.get("workers", []):
print(f"Worker {w['id']}: state={w.get('state')} slots={w.get('running_jobs')}/{w.get('capacity_slots')}")
coverage = client.coverage_list()
for c in coverage.get("coverage", []):
print(f"Share {c['share_id']}: protected={c.get('protected')} last_backup={c.get('last_backup_at')}")
AbacaClientError: HTTP 401
Symptom: Every API call raises AbacaClientError with status 401.
Likely cause: The Keystone token provided to Client(token=...) has expired, or the keystoneauth1 Session's auth plugin is not configured correctly.
Fix: Re-issue a token with openstack token issue and construct a new Client instance with the fresh token, or confirm your clouds.yaml credentials are valid and use Client(session=conn.session) so the session handles renewal automatically.
AbacaClientError: HTTP 403 on bucket_register / template_create
Symptom: Calls to client.bucket_register() or client.template_create() return HTTP 403.
Likely cause: These endpoints require a domain-scoped token. Your session or token is project-scoped.
Fix: Construct a domain-scoped session or token and pass it as api_session to enroll(), or supply it when constructing a separate Client instance for domain-owner operations. Refer to the enroll() docstring regarding the api_session parameter.
EnrollmentError: could not find the Abacá endpoint in the Keystone catalog
Symptom: find_abaca_endpoint(session) raises EnrollmentError with the message above.
Likely cause: Abacá is not registered in the Keystone service catalog for this cloud, or the service type name does not match. The expected service type is share-protection.
Fix: Verify Abacá is deployed and registered:
openstack catalog list | grep share-protection
If no entry appears, the Keystone identity setup step (deploy/rhoso/ scripts) has not been run or did not complete successfully. Re-run the identity registration script and confirm the service and endpoints appear in the catalog.
EnrollmentError: Abacá /v1/service_info did not return a service_user.id
Symptom: fetch_service_user_id() raises this error during enrollment.
Likely cause: The [abaca] service_user_id option is not set in the abaca-api configuration.
Fix: Confirm that service_user_id is populated in the [abaca] section of the API's .conf file, and that the abaca-api pod has been restarted after the configuration change. This value is set during Keystone identity setup.
EnrollmentError: could not store secret named '...': ...
Symptom: The enrollment helper raises EnrollmentError when calling store_s3_secrets() or store_ca_cert().
Likely cause: The Barbican endpoint is unreachable, the project-scoped session lacks creator role in Barbican, or Barbican itself is not operational.
Fix:
- Confirm Barbican is reachable:
openstack secret list - Confirm the tenant user has the
creatorrole in the project. - Check that
trust_rolesin yourEnrollmentRequestincludes"creator"(the default does).
Job stays in queued state indefinitely
Symptom: client.job_show(job_id) returns state=queued for longer than a few minutes after creating a backup.
Likely cause: No worker VMs are available (fleet is empty, workers are unhealthy, or capacity_slots are all occupied), or the abaca-conductor is not running.
Fix:
- Check the worker fleet:
client.worker_list()— if empty or all workers show no available slots, the conductor needs to boot new workers. - Confirm abaca-conductor is running in the
abacaOpenShift namespace. - Check conductor logs for reconciliation errors or fleet-boot failures. The conductor's
min_workerssetting controls the minimum fleet size. - If
error_categoryon the job isoperator_action_required, escalate to the operator — the tenant cannot self-resolve fleet-level issues.
AbacaClientError: HTTP 409 on policy create
Symptom: client.policy_create() returns HTTP 409.
Likely cause: A policy already exists for the specified share_id. Only one active policy per share is permitted.
Fix: List existing policies with client.policy_list() and either update the existing policy with client.policy_update() or delete it before creating a new one.
TLS verification failures (SSLError)
Symptom: Requests fail with an SSL certificate verification error when constructing Client with a token.
Likely cause: The Abacá API endpoint uses a certificate signed by a private CA that is not in the system trust store.
Fix: Pass the path to your CA bundle as ca_cert:
client = Client(
endpoint="https://abaca.internal.example.com",
token="<token>",
ca_cert="/etc/ssl/certs/internal-ca.pem",
)
When using a keystoneauth1 Session, configure the CA bundle on the session itself.